TypeScript聲明文件的語法與場景詳解
簡介
聲明文件是以.d.ts為后綴的文件,開發(fā)者在聲明文件中編寫類型聲明,TypeScript根據(jù)聲明文件的內(nèi)容進行類型檢查。(注意同目錄下最好不要有同名的.ts文件和.d.ts,例如lib.ts和lib.d.ts,否則模塊系統(tǒng)無法只根據(jù)文件名加載模塊)
為什么需要聲明文件呢?我們知道TypeScript根據(jù)類型聲明進行類型檢查,但有些情況可能沒有類型聲明:
- 第三方包,因為第三方包打包后都是JavaScript語法,而非TypeScript,沒有類型。
- 宿主環(huán)境擴展,如一些hybrid環(huán)境,在window變量下有一些bridge接口,這些接口沒有類型聲明。
如果沒有類型聲明,在使用變量、調(diào)用函數(shù)、實例化類的時候就沒法通過TypeScript的類型檢查。
聲明文件就是針對這些情況,開發(fā)者在聲明文件中編寫第三方模塊的類型聲明/宿主環(huán)境的類型聲明。讓TypeScript可以正常地進行類型檢查。
除此之外,聲明文件也可以被導(dǎo)入,使用其中暴露的類型定義。
總之,聲明文件有兩種用法:
- 被通過import導(dǎo)入,使用其中暴露的類型定義和變量聲明。
- 和相關(guān)模塊關(guān)聯(lián),為模塊進行類型聲明。
對于第二種用法,聲明文件如何同相關(guān)模塊關(guān)聯(lián)呢?
比如有個第三方包名字叫"foo",那么TypeScript會在node_modules/foo中根據(jù)其package.json的types和typing字段查找聲明文件查找到的聲明文件被作為該模塊的聲明文件;TypeScript也會在node_modules/@types/foo/目錄中查找聲明文件,如果能找到就被作為foo模塊的聲明文件;TypeScript還會在我們的項目中查找.d.ts文件,如果遇到declare module 'foo'語句,則該聲明被用作foo模塊的聲明。
總結(jié)一下,TypeScript會在特定的目錄讀取指定的聲明文件。
- 在內(nèi)部項目中,TypeScript會讀取tsconfig.json中的文件集合,在其中的聲明文件才會被處理。
- 讀取node_modules中各第三方包的package.json的types或者typing指定的文件。
- 讀取@types目錄下同名包的聲明文件。
聲明文件中的代碼不會出現(xiàn)在最終的編譯結(jié)果中,編譯后會把轉(zhuǎn)換后的JavaScript代碼輸出到"outDir"選項指定的目錄中,并且把 .ts模塊中使用到的值的聲明都輸出到"declarationDir"指定的目錄中。
而在.ts文件中的聲明語句,編譯后會被去掉,如
declare let a: number; export default a;
會被編譯為
"use strict"; exports.__esModule = true; exports["default"] = a;
TypeScript編譯過程不僅將TypeScript語法轉(zhuǎn)譯為ES6/ES5,還會將代碼中.ts文件中用到的值的類型輸出到指定的聲明文件中。如果你需要實現(xiàn)一個庫項目,這個功能很有用,因為用到你的庫的項目可以直接使用這些聲明文件,而不需要你再為你的庫寫聲明文件。
語法
內(nèi)容
TypeScript中的聲明會創(chuàng)建以下三種實體之一:命名空間,類型或值。
命名空間最終被編譯為全局變量,因此我們也可以認(rèn)為聲明文件中其實創(chuàng)建了類型和值兩種實體。即定義類型或者聲明值。
// 類型 接口 interface Person {name: string;} // 類型 類型別名 type Fruit = {size: number}; // 值 變量 declare let a: number; // 值 函數(shù) declare function log(message: string): void; // 值 類 declare class Person {name: string;} // 值 枚舉 declare enum Color {Red, Green} // 值 命名空間 declare namespace person {let name: string;}
我們注意到類型可以直接定義,但是值的聲明需要借助declare關(guān)鍵字,這是因為如果不用declare關(guān)鍵字,值的聲明和初始化是一起的,如
let a: number; // 編譯為 var a;
但是編譯結(jié)果是會去掉所有的聲明語句,保留初始化的部分,而聲明文件中的內(nèi)容只是起聲明作用,因此需要通過declare來標(biāo)識,這只是聲明語句,編譯時候直接去掉即可。
TypeScript也約束聲明文件中聲明一個值必須要用declare,否則會被認(rèn)為存在初始化的內(nèi)容,從而報錯。
// foo.d.ts let a: number = 1; // error TS1039: Initializers are not allowed in ambient contexts.
declare也允許出現(xiàn)在.ts文件中,但一般不會這么做,.ts文件中直接用let/const/function/class就可以聲明并初始化一個變量。并且.ts文件編譯后也會去掉declare的語句,所以不需要declare語句。
注意,declare多個同名的變量是會沖突的
declare let foo: number; // error TS2451: Cannot redeclare block-scoped variable 'a'. declare let foo: number; // error TS2451: Cannot redeclare block-scoped variable 'a'.
除了使用declare聲明一個值,declare還可以用來聲明一個模塊和全局的插件,這兩種用法都是在特定場景用來給第三方包做聲明。
declare module用來給一個第三方模進行類型聲明,比如有一個第三方包foo,沒有類型聲明。我們可以在我們項目中實現(xiàn)一個聲明文件來讓TypeScript可以識別模塊類型:foo.d.ts
// foo.d.ts declare module 'foo' { export let size: number; }
然后我們就可以使用了:
import foo from 'foo'; console.log(foo.size);
declare module除了可以用來給一個模塊聲明類型,還可以用來實現(xiàn)模塊插件的聲明。后面小節(jié)中會做介紹。
declare global用來給擴展全局的第三方包進行聲明,后面小節(jié)介紹。
模塊化
模塊語法
聲明文件的模塊化語法和.ts模塊的類似,在一些細節(jié)上稍有不同。.ts導(dǎo)出的是模塊(typescript會根據(jù)導(dǎo)出的模塊判斷類型),.d.ts導(dǎo)出的是類型的定義和聲明的值。
聲明文件可以導(dǎo)出類型,也可以導(dǎo)出值的聲明
// index.d.ts // 導(dǎo)出值聲明 export let a: number; // 導(dǎo)出類型 export interface Person { name: string; };
聲明文件可以引入其他的聲明文件,甚至可以引入其他的.ts文件(因為.ts文件也可能導(dǎo)出類型)
// Person.d.ts export default interface Person {name: string} // index.d.ts import Person from './person'; export let p: Person;
如果聲明文件不導(dǎo)出,默認(rèn)是全局可以訪問的
// person.d.ts interface Person {name: string} declare let p: Person; // index.ts let p1: Person = {name: 'Sam'}; console.log(p);
如果使用模塊導(dǎo)出語法(ESM/CommJS/UMD),則不解析為全局(當(dāng)然UMD還是可以全局訪問)。
// ESM interface Person {name: string} export let p: Person; export default Person;
// CommonJS interface Person {name: string} declare let p: Person; export = p;
// UMD interface Person {name: string} declare let p: Person; export = p; export as namespace p;
注意:UMD包export as namespace語法只能在聲明文件中出現(xiàn)。
三斜線指令
聲明文件中的三斜線指令,用于控制編譯過程。
三斜線指令僅可放在包含它的文件的最頂端。
如果指定--noResove編譯選項,預(yù)編譯過程會忽略三斜線指令。
reference
reference指令用來表明聲明文件的依賴情況。
/// <reference path="..." />用來告訴編譯器依賴的其他聲明文件。編譯器預(yù)處理時候會將path指定的聲明文件加入進來。路徑是相對于文件自身的。引用不存在的文件或者引用自身,會報錯。
/// <reference types="node" />用來告訴編譯器它依賴node_modules/@types/node/index.d.ts。如果你的項目里面依賴了@types中的某些聲明文件,那么編譯后輸出的聲明文件中會自動加上這個指令,用以說明你的項目中的聲明文件依賴了@types中相關(guān)的聲明文件。
/// <reference no-default-lib="true"/>,
這涉及兩個編譯選項,--noLib,設(shè)置了這個編譯選項后,編譯器會忽略默認(rèn)庫,默認(rèn)庫是在安裝TypeScript時候自動引入的,這個文件包含 JavaScript 運行時(如window)以及 DOM 中存在各種常見的環(huán)境聲明。但是如果你的項目運行環(huán)境和基于標(biāo)準(zhǔn)瀏覽器運行時環(huán)境有很大不同,可能需要排除默認(rèn)庫,一旦你排除了默認(rèn)的 lib.d.ts 文件,你就可以在編譯上下文中包含一個命名相似的文件,TypeScript 將提取該文件進行類型檢查。
另一個編譯選項是--skipDefaultLibCheck這個選項會讓編譯器忽略包含了/// <reference no-default-lib="true"/>指令的聲明文件。你會注意到在默認(rèn)庫的頂端都會有這個三斜線指令,因此如果采用了--skipDefaultLibCheck編譯選項,也同樣會忽略默認(rèn)庫。
amd-module
amd-module相關(guān)指令用于控制打包到amd模塊的編譯過程
///<amd-module name='NamedModule'/>這個指令用于告訴編譯器給打包為AMD的模塊傳入模塊名(默認(rèn)情況是匿名的)
///<amd-module name='NamedModule'/> export class C { }
編譯結(jié)果為
define("NamedModule", ["require", "exports"], function (require, exports) { var C = (function () { function C() { } return C; })(); exports.C = C; });
場景
這里我們將自己的項目代碼稱為“內(nèi)部項目”,引入的第三方模塊,包括npm引入的和script引入的,稱為“外部模塊”。
1. 在內(nèi)部項目中給內(nèi)部項目寫聲明文件
自己項目中,給自己的模塊寫聲明文件,例如多個模塊共享的類型,就可以寫一個聲明文件。這種場景通常不必要,一般是某個.ts文件導(dǎo)出聲明,其他模塊引用聲明。
2. 給第三方包寫聲明文件
給第三方包寫聲明文件又分為在內(nèi)部項目中給第三方包寫聲明文件和在外部模塊中給外部模塊寫聲明文件。
在內(nèi)部項目中給第三方包寫聲明文件: 如果第三方包沒有TS聲明文件,則為了保證使用第三方包時候能夠通過類型檢查,也為了安全地使用第三方包,需要在內(nèi)部項目中寫第三方包的聲明文件。
在外部模塊中給外部模塊寫聲明文件: 如果你是第三方庫的作者,無論你是否使用TypeScript開發(fā)庫,都應(yīng)該提供聲明文件以便用TypeScript開發(fā)的項目能夠更好地使用你的庫,那么你就需要寫好你的聲明文件。
這兩種情況的聲明文件的語法類似,只在個別聲明語法和文件的處理上有區(qū)別:
- 內(nèi)部項目給第三方包寫聲明文件時候,以.d.ts命名即可,然后在tsconfig.json中的files和include中配置能夠包含到文件即可,外部模塊的聲明文件需要打包到輸出目錄,并且在package.json中的type字段指定聲明文件位置;或者上傳到@types/<moduleName>中,使用者通過npm install @types/<moduleName>安裝聲明文件。redux就在tsconfig.json中指定了declarationDir為./types,TypeScript會將項目的聲明都打包到這個目錄下,目錄結(jié)構(gòu)和源碼一樣,然后redux源碼入口處導(dǎo)出了所有的模塊,因此types目錄下也有一個入口的聲明文件index.d.ts,并且包含了所有的導(dǎo)出模塊聲明,redux在package.json中指定types字段(或者typings字段)為入口的聲明文件:./types/index.d.ts。這樣就實現(xiàn)了自動生成接口的聲明文件。
- 內(nèi)部項目給第三方寫聲明文件時候,如果是通過npm模塊引入方式,如import moduleName from 'path';則需要通過declare module '<moduleName>'語法來聲明模塊。而外部模塊的聲明文件都是正常的類型導(dǎo)出語法(如export default export =等),如果聲明文件在@types中,會將與模塊同名的聲明文件作為模塊的類型聲明;如果聲明文件在第三方包中,那么就TypeScript模塊就將它作為這個第三方包模塊的模塊聲明,當(dāng)使用者導(dǎo)入并使用這個模塊時候,TypeScript就根據(jù)相應(yīng)地聲明文件進行類型提示和類型檢查。
根據(jù)第三方包類型可以分成幾種
全局變量的第三方庫
我們知道如果不使用模塊導(dǎo)出語法,聲明文件默認(rèn)的聲明都是全局的。
declare namespace person { let name: string }
或者
interface Person { name: string; } declare let person: Person;
使用:
console.log(person.name);
修改全局變量的模塊的第三方庫的聲明
如果有第三方包修改了一個全局模塊(這個第三方包是這個全局模塊的插件),這個第三方包的聲明文件根據(jù)全局模塊的聲明,有不同的聲明方式
如果全局模塊使用命名空間聲明
declare namespace person { let name: string }
根據(jù)命名空間的聲明合并原理,插件模塊可以這樣聲明
declare namespace person { // 擴展了age屬性 let age: number; }
如果全局模塊使用全局變量聲明
interface Person { name: string; } declare let person: Person;
根據(jù)接口的聲明合并原理,插件模塊可以這樣聲明
interface Person { // 擴展了age屬性 age: number; }
上面的全局模塊的插件模塊的聲明方式可以應(yīng)用于下面的場景:
- 內(nèi)部項目使用了插件,但插件沒有聲明文件,我們可以在內(nèi)部項目中自己實現(xiàn)聲明文件。
- 給插件模塊寫聲明文件并發(fā)布到@types。
如果是插件模塊的作者,希望在項目中引用全局模塊并且將擴展的類型輸出到聲明文件,以便其他項目使用。可以這樣實現(xiàn)
// plugin/index.ts // 注意這樣聲明才會讓TypeScript將類型輸出聲明文件 declare global { // 假設(shè)全局模塊使用全局變量的方式聲明 interface Person { age: number } } console.log(person.age); export {};
注意,declare global寫在聲明文件中也可以,但是要在尾部加上export {}或者其他的模塊導(dǎo)出語句,否則會報錯。另外declare global在聲明文件中寫的話,編譯后不會輸出到聲明文件中。
修改window
window的類型是interface Window {...},在默認(rèn)庫中聲明,如果要擴展window變量(如一些hybrid環(huán)境)可以這樣實現(xiàn)
// window.d.ts // 聲明合并 interface Window { bridge: {log(): void} } // 或者 declare global { interface Window { bridge: {log(): void} } }
或者
// index.ts declare global { interface Window { bridge: {log(): void} } } window.bridge = {log() {}} export {};
ESM和CommonJS
給第三方的ESM或者CommonJS模塊寫聲明文件,使用ESM導(dǎo)出或者CommonJS模塊語法導(dǎo)出都可以,不管第三方包是哪種模塊形式。
看下面示例
interface Person { name: string; } declare let person: Person; export = person; // 也可以使用export default person;
import person from 'person'; console.log(person.name);
上面的聲明文件是放在node_modules/@types/person/index.d.ts中,或者放在node_modules/person/package.json的types或者typings字段指定的位置。
如果在自己項目中聲明,應(yīng)該使用declare module實現(xiàn)
declare module 'person' { export let name: string; }
UMD
UMD模塊,在CommonJS聲明的基礎(chǔ)上加上export as namespace ModuleName;語句即可。
看下面的ESM的例子
// node_modules/@types/person/index.d.ts interface Person { name: string; } declare let person: Person; export default person; export as namespace person;
可以通過import導(dǎo)入來訪問
// src/index.ts import person from 'person'; console.log(person.name);
也可以通過全局訪問
// src/index.ts // 注意如果用ESM導(dǎo)出,全局使用時候先訪問defalut屬性。 console.log(person.default.name);
下面是CommonJS的例子
// node_modules/@types/person/index.d.ts interface Person { name: string; } declare let person: Person; export default person; export as namespace person;
可以通過import引入訪問
// src/index.ts import person from 'person'; console.log(person.name);
也可以全局訪問
// src/index.ts console.log(person.name);
模塊插件
上面我們提到,declare module不僅可以用于給一個第三方模塊聲明類型,還可以用來給第三方模塊的插件模塊聲明類型。
// types/moment-plugin/index.d.ts // 如果moment定義為UMD,就不需要引入,直接能夠使用 import * as moment from 'moment'; declare module 'moment' { export function foo(): moment.CalendarKey; } // src/index.ts import * as moment from 'moment'; import 'moment-plugin'; moment.foo();
比如作為redux的插件的redux-thunk的聲明文件extend-redux.d.ts,就是這樣聲明的
// node_modules/redux-thunk/extend-redux.d.ts declare module 'redux' { // declaration code...... }
總結(jié)
到此這篇關(guān)于TypeScript聲明文件的語法與場景詳解的文章就介紹到這了,更多相關(guān)TS聲明文件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Echarts?graph關(guān)系圖的使用入門級教程
近期需要使用echarts關(guān)系圖,這里給大家總結(jié)下,這篇文章主要給大家介紹了關(guān)于Echarts?graph關(guān)系圖使用的相關(guān)資料,文中給出了詳細的代碼介紹,需要的朋友可以參考下2024-01-01使用JavaScript進行進制轉(zhuǎn)換將字符串轉(zhuǎn)換為十進制
JS 是一個很神奇的語言,可以將任意進制字符串轉(zhuǎn)換為十進制,如二進制,八進制,十六進制, 第二數(shù)數(shù)不寫即為最常用的轉(zhuǎn)換為整型十進制2014-09-09有趣的script標(biāo)簽用getAttribute方法來自腳本吧
有趣的script標(biāo)簽用getAttribute方法來自腳本吧...2007-03-03