黑河商城網(wǎng)站建設(shè)/東莞網(wǎng)絡(luò)推廣平臺(tái)
- declare var?聲明全局變量
- declare function?聲明全局方法
- declare class?聲明全局類
- declare enum?聲明全局枚舉類型
- declare namespace?聲明(含有子屬性的)全局對(duì)象
- interface?和?type?聲明全局類型
- export?導(dǎo)出變量
- export namespace?導(dǎo)出(含有子屬性的)對(duì)象
- export default?ES6 默認(rèn)導(dǎo)出
- export =?commonjs 導(dǎo)出模塊
- export as namespace?UMD 庫聲明全局變量
- declare global?擴(kuò)展全局變量
- declare module?擴(kuò)展模塊
- /// <reference />?三斜線指令
什么是聲明語句§
假如我們想使用第三方庫 jQuery,一種常見的方式是在 html 中通過?<script>
?標(biāo)簽引入 jQuery,然后就可以使用全局變量?$
?或?jQuery
?了。
我們通常這樣獲取一個(gè)?id
?是?foo
?的元素:
$('#foo');
// or
jQuery('#foo');
但是在 ts 中,編譯器并不知道?$
?或?jQuery
?是什么東西1:
jQuery('#foo');
// ERROR: Cannot find name 'jQuery'.
這時(shí),我們需要使用?declare var
?來定義它的類型2:
declare var jQuery: (selector: string) => any;jQuery('#foo');
上例中,declare var
?并沒有真的定義一個(gè)變量,只是定義了全局變量?jQuery
?的類型,僅僅會(huì)用于編譯時(shí)的檢查,在編譯結(jié)果中會(huì)被刪除。它編譯結(jié)果是:
jQuery('#foo');
除了?declare var
?之外,還有其他很多種聲明語句,將會(huì)在后面詳細(xì)介紹。
什么是聲明文件§
通常我們會(huì)把聲明語句放到一個(gè)單獨(dú)的文件(jQuery.d.ts
)中,這就是聲明文件3:
// src/jQuery.d.tsdeclare var jQuery: (selector: string) => any;
// src/index.tsjQuery('#foo');
聲明文件必需以?.d.ts
?為后綴。
一般來說,ts 會(huì)解析項(xiàng)目中所有的?*.ts
?文件,當(dāng)然也包含以?.d.ts
?結(jié)尾的文件。所以當(dāng)我們將?jQuery.d.ts
?放到項(xiàng)目中時(shí),其他所有?*.ts
?文件就都可以獲得?jQuery
?的類型定義了。
/path/to/project
├── src
| ├── index.ts
| └── jQuery.d.ts
└── tsconfig.json
假如仍然無法解析,那么可以檢查下?tsconfig.json
?中的?files
、include
?和?exclude
?配置,確保其包含了?jQuery.d.ts
?文件。
這里只演示了全局變量這種模式的聲明文件,假如是通過模塊導(dǎo)入的方式使用第三方庫的話,那么引入聲明文件又是另一種方式了,將會(huì)在后面詳細(xì)介紹。
第三方聲明文件§
當(dāng)然,jQuery 的聲明文件不需要我們定義了,社區(qū)已經(jīng)幫我們定義好了:jQuery in DefinitelyTyped。
我們可以直接下載下來使用,但是更推薦的是使用?@types
?統(tǒng)一管理第三方庫的聲明文件。
@types
?的使用方式很簡(jiǎn)單,直接用 npm 安裝對(duì)應(yīng)的聲明模塊即可,以 jQuery 舉例:
npm install @types/jquery --save-dev
可以在這個(gè)頁面搜索你需要的聲明文件。
書寫聲明文件§
當(dāng)一個(gè)第三方庫沒有提供聲明文件時(shí),我們就需要自己書寫聲明文件了。前面只介紹了最簡(jiǎn)單的聲明文件內(nèi)容,而真正書寫一個(gè)聲明文件并不是一件簡(jiǎn)單的事,以下會(huì)詳細(xì)介紹如何書寫聲明文件。
在不同的場(chǎng)景下,聲明文件的內(nèi)容和使用方式會(huì)有所區(qū)別。
庫的使用場(chǎng)景主要有以下幾種:
- 全局變量:通過?
<script>
?標(biāo)簽引入第三方庫,注入全局變量 - npm 包:通過?
import foo from 'foo'
?導(dǎo)入,符合 ES6 模塊規(guī)范 - UMD 庫:既可以通過?
<script>
?標(biāo)簽引入,又可以通過?import
?導(dǎo)入 - 直接擴(kuò)展全局變量:通過?
<script>
?標(biāo)簽引入后,改變一個(gè)全局變量的結(jié)構(gòu) - 在 npm 包或 UMD 庫中擴(kuò)展全局變量:引用 npm 包或 UMD 庫后,改變一個(gè)全局變量的結(jié)構(gòu)
- 模塊插件:通過?
<script>
?或?import
?導(dǎo)入后,改變另一個(gè)模塊的結(jié)構(gòu)
全局變量§
全局變量是最簡(jiǎn)單的一種場(chǎng)景,之前舉的例子就是通過?<script>
?標(biāo)簽引入 jQuery,注入全局變量?$
?和?jQuery
。
使用全局變量的聲明文件時(shí),如果是以?npm install @types/xxx --save-dev
?安裝的,則不需要任何配置。如果是將聲明文件直接存放于當(dāng)前項(xiàng)目中,則建議和其他源碼一起放到?src
?目錄下(或者對(duì)應(yīng)的源碼目錄下):
/path/to/project
├── src
| ├── index.ts
| └── jQuery.d.ts
└── tsconfig.json
如果沒有生效,可以檢查下?tsconfig.json
?中的?files
、include
?和?exclude
?配置,確保其包含了?jQuery.d.ts
?文件。
全局變量的聲明文件主要有以下幾種語法:
- declare var?聲明全局變量
- declare function?聲明全局方法
- declare class?聲明全局類
- declare enum?聲明全局枚舉類型
- declare namespace?聲明(含有子屬性的)全局對(duì)象
- interface?和?type?聲明全局類型
declare var
§
在所有的聲明語句中,declare var
?是最簡(jiǎn)單的,如之前所學(xué),它能夠用來定義一個(gè)全局變量的類型。與其類似的,還有?declare let
?和?declare const
,使用?let
?與使用?var
?沒有什么區(qū)別:
// src/jQuery.d.tsdeclare let jQuery: (selector: string) => any;
// src/index.tsjQuery('#foo');
// 使用 declare let 定義的 jQuery 類型,允許修改這個(gè)全局變量
jQuery = function(selector) {return document.querySelector(selector);
};
而當(dāng)我們使用?const
?定義時(shí),表示此時(shí)的全局變量是一個(gè)常量,不允許再去修改它的值了4:
// src/jQuery.d.tsdeclare const jQuery: (selector: string) => any;jQuery('#foo');
// 使用 declare const 定義的 jQuery 類型,禁止修改這個(gè)全局變量
jQuery = function(selector) {return document.querySelector(selector);
};
// ERROR: Cannot assign to 'jQuery' because it is a constant or a read-only property.
一般來說,全局變量都是禁止修改的常量,所以大部分情況都應(yīng)該使用?const
?而不是?var
?或?let
。
需要注意的是,聲明語句中只能定義類型,切勿在聲明語句中定義具體的實(shí)現(xiàn)5:
declare const jQuery = function(selector) {return document.querySelector(selector);
};
// ERROR: An implementation cannot be declared in ambient contexts.
declare function
§
declare function
?用來定義全局函數(shù)的類型。jQuery 其實(shí)就是一個(gè)函數(shù),所以也可以用?function
?來定義:
// src/jQuery.d.tsdeclare function jQuery(selector: string): any;
// src/index.tsjQuery('#foo');
在函數(shù)類型的聲明語句中,函數(shù)重載也是支持的6:
// src/jQuery.d.tsdeclare function jQuery(selector: string): any;
declare function jQuery(domReadyCallback: () => any): any;
// src/index.tsjQuery('#foo');
jQuery(function() {alert('Dom Ready!');
});
declare class
§
當(dāng)全局變量是一個(gè)類的時(shí)候,我們用?declare class
?來定義它的類型7:
// src/Animal.d.tsdeclare class Animal {name: string;constructor(name: string);sayHi(): string;
}
// src/index.tslet cat = new Animal('Tom');
同樣的,declare class
?語句也只能用來定義類型,不能用來定義具體的實(shí)現(xiàn),比如定義?sayHi
?方法的具體實(shí)現(xiàn)則會(huì)報(bào)錯(cuò):
// src/Animal.d.tsdeclare class Animal {name: string;constructor(name: string);sayHi() {return `My name is ${this.name}`;};// ERROR: An implementation cannot be declared in ambient contexts.
}
declare enum
§
使用?declare enum
?定義的枚舉類型也稱作外部枚舉(Ambient Enums),舉例如下8:
// src/Directions.d.tsdeclare enum Directions {Up,Down,Left,Right
}
// src/index.tslet directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
與其他全局變量的類型聲明一致,declare enum
?僅用來定義類型,而不是具體的值。
Directions.d.ts
?僅僅會(huì)用于編譯時(shí)的檢查,聲明文件里的內(nèi)容在編譯結(jié)果中會(huì)被刪除。它編譯結(jié)果是:
var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
其中?Directions
?是由第三方庫定義好的全局變量。
declare namespace
§
namespace
?是 ts 早期時(shí)為了解決模塊化而創(chuàng)造的關(guān)鍵字,中文稱為命名空間。
由于歷史遺留原因,在早期還沒有 ES6 的時(shí)候,ts 提供了一種模塊化方案,使用?module
?關(guān)鍵字表示內(nèi)部模塊。但由于后來 ES6 也使用了?module
?關(guān)鍵字,ts 為了兼容 ES6,使用?namespace
?替代了自己的?module
,更名為命名空間。
隨著 ES6 的廣泛應(yīng)用,現(xiàn)在已經(jīng)不建議再使用 ts 中的?namespace
,而推薦使用 ES6 的模塊化方案了,故我們不再需要學(xué)習(xí)?namespace
?的使用了。
namespace
?被淘汰了,但是在聲明文件中,declare namespace
?還是比較常用的,它用來表示全局變量是一個(gè)對(duì)象,包含很多子屬性。
比如?jQuery
?是一個(gè)全局變量,它是一個(gè)對(duì)象,提供了一個(gè)?jQuery.ajax
?方法可以調(diào)用,那么我們就應(yīng)該使用?declare namespace jQuery
?來聲明這個(gè)擁有多個(gè)子屬性的全局變量。
// src/jQuery.d.tsdeclare namespace jQuery {function ajax(url: string, settings?: any): void;
}
// src/index.tsjQuery.ajax('/api/get_something');
注意,在?declare namespace
?內(nèi)部,我們直接使用?function ajax
?來聲明函數(shù),而不是使用?declare function ajax
。類似的,也可以使用?const
,?class
,?enum
?等語句9:
// src/jQuery.d.tsdeclare namespace jQuery {function ajax(url: string, settings?: any): void;const version: number;class Event {blur(eventType: EventType): void}enum EventType {CustomClick}
}
// src/index.tsjQuery.ajax('/api/get_something');
console.log(jQuery.version);
const e = new jQuery.Event();
e.blur(jQuery.EventType.CustomClick);
嵌套的命名空間§
如果對(duì)象擁有深層的層級(jí),則需要用嵌套的?namespace
?來聲明深層的屬性的類型10:
// src/jQuery.d.tsdeclare namespace jQuery {function ajax(url: string, settings?: any): void;namespace fn {function extend(object: any): void;}
}
// src/index.tsjQuery.ajax('/api/get_something');
jQuery.fn.extend({check: function() {return this.each(function() {this.checked = true;});}
});
假如?jQuery
?下僅有?fn
?這一個(gè)屬性(沒有?ajax
?等其他屬性或方法),則可以不需要嵌套?namespace
11:
// src/jQuery.d.tsdeclare namespace jQuery.fn {function extend(object: any): void;
}
// src/index.tsjQuery.fn.extend({check: function() {return this.each(function() {this.checked = true;});}
});
interface
?和?type
§
除了全局變量之外,可能有一些類型我們也希望能暴露出來。在類型聲明文件中,我們可以直接使用?interface
?或?type
?來聲明一個(gè)全局的接口或類型12:
// src/jQuery.d.tsinterface AjaxSettings {method?: 'GET' | 'POST'data?: any;
}
declare namespace jQuery {function ajax(url: string, settings?: AjaxSettings): void;
}
這樣的話,在其他文件中也可以使用這個(gè)接口或類型了:
// src/index.tslet settings: AjaxSettings = {method: 'POST',data: {name: 'foo'}
};
jQuery.ajax('/api/post_something', settings);
type
?與?interface
?類似,不再贅述。
防止命名沖突§
暴露在最外層的?interface
?或?type
?會(huì)作為全局類型作用于整個(gè)項(xiàng)目中,我們應(yīng)該盡可能的減少全局變量或全局類型的數(shù)量。故最好將他們放到?namespace
?下13:
// src/jQuery.d.tsdeclare namespace jQuery {interface AjaxSettings {method?: 'GET' | 'POST'data?: any;}function ajax(url: string, settings?: AjaxSettings): void;
}
注意,在使用這個(gè)?interface
?的時(shí)候,也應(yīng)該加上?jQuery
?前綴:
// src/index.tslet settings: jQuery.AjaxSettings = {method: 'POST',data: {name: 'foo'}
};
jQuery.ajax('/api/post_something', settings);
聲明合并§
假如 jQuery 既是一個(gè)函數(shù),可以直接被調(diào)用?jQuery('#foo')
,又是一個(gè)對(duì)象,擁有子屬性?jQuery.ajax()
(事實(shí)確實(shí)如此),那么我們可以組合多個(gè)聲明語句,它們會(huì)不沖突的合并起來14:
// src/jQuery.d.tsdeclare function jQuery(selector: string): any;
declare namespace jQuery {function ajax(url: string, settings?: any): void;
}
// src/index.tsjQuery('#foo');
jQuery.ajax('/api/get_something');
關(guān)于聲明合并的更多用法,可以查看聲明合并章節(jié)。
npm 包§
一般我們通過?import foo from 'foo'
?導(dǎo)入一個(gè) npm 包,這是符合 ES6 模塊規(guī)范的。
在我們嘗試給一個(gè) npm 包創(chuàng)建聲明文件之前,需要先看看它的聲明文件是否已經(jīng)存在。一般來說,npm 包的聲明文件可能存在于兩個(gè)地方:
- 與該 npm 包綁定在一起。判斷依據(jù)是?
package.json
?中有?types
?字段,或者有一個(gè)?index.d.ts
?聲明文件。這種模式不需要額外安裝其他包,是最為推薦的,所以以后我們自己創(chuàng)建 npm 包的時(shí)候,最好也將聲明文件與 npm 包綁定在一起。 - 發(fā)布到?
@types
?里。我們只需要嘗試安裝一下對(duì)應(yīng)的?@types
?包就知道是否存在該聲明文件,安裝命令是?npm install @types/foo --save-dev
。這種模式一般是由于 npm 包的維護(hù)者沒有提供聲明文件,所以只能由其他人將聲明文件發(fā)布到?@types
?里了。
假如以上兩種方式都沒有找到對(duì)應(yīng)的聲明文件,那么我們就需要自己為它寫聲明文件了。由于是通過?import
?語句導(dǎo)入的模塊,所以聲明文件存放的位置也有所約束,一般有兩種方案:
- 創(chuàng)建一個(gè)?
node_modules/@types/foo/index.d.ts
?文件,存放?foo
?模塊的聲明文件。這種方式不需要額外的配置,但是?node_modules
?目錄不穩(wěn)定,代碼也沒有被保存到倉庫中,無法回溯版本,有不小心被刪除的風(fēng)險(xiǎn),故不太建議用這種方案,一般只用作臨時(shí)測(cè)試。 - 創(chuàng)建一個(gè)?
types
?目錄,專門用來管理自己寫的聲明文件,將?foo
?的聲明文件放到?types/foo/index.d.ts
?中。這種方式需要配置下?tsconfig.json
?中的?paths
?和?baseUrl
?字段。
目錄結(jié)構(gòu):
/path/to/project
├── src
| └── index.ts
├── types
| └── foo
| └── index.d.ts
└── tsconfig.json
tsconfig.json
?內(nèi)容:
{"compilerOptions": {"module": "commonjs","baseUrl": "./","paths": {"*": ["types/*"]}}
}
如此配置之后,通過?import
?導(dǎo)入?foo
?的時(shí)候,也會(huì)去?types
?目錄下尋找對(duì)應(yīng)的模塊的聲明文件了。
注意?module
?配置可以有很多種選項(xiàng),不同的選項(xiàng)會(huì)影響模塊的導(dǎo)入導(dǎo)出模式。這里我們使用了?commonjs
?這個(gè)最常用的選項(xiàng),后面的教程也都默認(rèn)使用的這個(gè)選項(xiàng)。
不管采用了以上兩種方式中的哪一種,我都強(qiáng)烈建議大家將書寫好的聲明文件(通過給第三方庫發(fā) pull request,或者直接提交到?@types
?里)發(fā)布到開源社區(qū)中,享受了這么多社區(qū)的優(yōu)秀的資源,就應(yīng)該在力所能及的時(shí)候給出一些回饋。只有所有人都參與進(jìn)來,才能讓 ts 社區(qū)更加繁榮。
npm 包的聲明文件主要有以下幾種語法:
- export?導(dǎo)出變量
- export namespace?導(dǎo)出(含有子屬性的)對(duì)象
- export default?ES6 默認(rèn)導(dǎo)出
- export =?commonjs 導(dǎo)出模塊
export
§
npm 包的聲明文件與全局變量的聲明文件有很大區(qū)別。在 npm 包的聲明文件中,使用?declare
?不再會(huì)聲明一個(gè)全局變量,而只會(huì)在當(dāng)前文件中聲明一個(gè)局部變量。只有在聲明文件中使用?export
?導(dǎo)出,然后在使用方?import
?導(dǎo)入后,才會(huì)應(yīng)用到這些類型聲明。
export
?的語法與普通的 ts 中的語法類似,區(qū)別僅在于聲明文件中禁止定義具體的實(shí)現(xiàn)15:
// types/foo/index.d.tsexport const name: string;
export function getName(): string;
export class Animal {constructor(name: string);sayHi(): string;
}
export enum Directions {Up,Down,Left,Right
}
export interface Options {data: any;
}
對(duì)應(yīng)的導(dǎo)入和使用模塊應(yīng)該是這樣:
// src/index.tsimport { name, getName, Animal, Directions, Options } from 'foo';console.log(name);
let myName = getName();
let cat = new Animal('Tom');
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
let options: Options = {data: {name: 'foo'}
};
混用?declare
?和?export
§
我們也可以使用?declare
?先聲明多個(gè)變量,最后再用?export
?一次性導(dǎo)出。上例的聲明文件可以等價(jià)的改寫為16:
// types/foo/index.d.tsdeclare const name: string;
declare function getName(): string;
declare class Animal {constructor(name: string);sayHi(): string;
}
declare enum Directions {Up,Down,Left,Right
}
interface Options {data: any;
}export { name, getName, Animal, Directions, Options };
注意,與全局變量的聲明文件類似,interface
?前是不需要?declare
?的。
export namespace
§
與?declare namespace
?類似,export namespace
?用來導(dǎo)出一個(gè)擁有子屬性的對(duì)象17:
// types/foo/index.d.tsexport namespace foo {const name: string;namespace bar {function baz(): string;}
}
// src/index.tsimport { foo } from 'foo';console.log(foo.name);
foo.bar.baz();
export default
§
在 ES6 模塊系統(tǒng)中,使用?export default
?可以導(dǎo)出一個(gè)默認(rèn)值,使用方可以用?import foo from 'foo'
?而不是?import { foo } from 'foo'
?來導(dǎo)入這個(gè)默認(rèn)值。
在類型聲明文件中,export default
?用來導(dǎo)出默認(rèn)值的類型18:
// types/foo/index.d.tsexport default function foo(): string;
// src/index.tsimport foo from 'foo';foo();
注意,只有?function
、class
?和?interface
?可以直接默認(rèn)導(dǎo)出,其他的變量需要先定義出來,再默認(rèn)導(dǎo)出19:
// types/foo/index.d.tsexport default enum Directions {
// ERROR: Expression expected.Up,Down,Left,Right
}
上例中?export default enum
?是錯(cuò)誤的語法,需要使用?declare enum
?定義出來,然后使用?export default
?導(dǎo)出:
// types/foo/index.d.tsdeclare enum Directions {Up,Down,Left,Right
}export default Directions;
針對(duì)這種默認(rèn)導(dǎo)出,我們一般會(huì)將導(dǎo)出語句放在整個(gè)聲明文件的最前面20:
// types/foo/index.d.tsexport default Directions;declare enum Directions {Up,Down,Left,Right
}
export =
§
在 commonjs 規(guī)范中,我們用以下方式來導(dǎo)出一個(gè)模塊:
// 整體導(dǎo)出
module.exports = foo;
// 單個(gè)導(dǎo)出
exports.bar = bar;
在 ts 中,針對(duì)這種模塊導(dǎo)出,有多種方式可以導(dǎo)入,第一種方式是?const ... = require
:
// 整體導(dǎo)入
const foo = require('foo');
// 單個(gè)導(dǎo)入
const bar = require('foo').bar;
第二種方式是?import ... from
,注意針對(duì)整體導(dǎo)出,需要使用?import * as
?來導(dǎo)入:
// 整體導(dǎo)入
import * as foo from 'foo';
// 單個(gè)導(dǎo)入
import { bar } from 'foo';
第三種方式是?import ... require
,這也是 ts 官方推薦的方式:
// 整體導(dǎo)入
import foo = require('foo');
// 單個(gè)導(dǎo)入
import bar = foo.bar;
對(duì)于這種使用 commonjs 規(guī)范的庫,假如要為它寫類型聲明文件的話,就需要使用到?export =
?這種語法了21:
// types/foo/index.d.tsexport = foo;declare function foo(): string;
declare namespace foo {const bar: number;
}
需要注意的是,上例中使用了?export =
?之后,就不能再單個(gè)導(dǎo)出?export { bar }
?了。所以我們通過聲明合并,使用?declare namespace foo
?來將?bar
?合并到?foo
?里。
準(zhǔn)確地講,export =
?不僅可以用在聲明文件中,也可以用在普通的 ts 文件中。實(shí)際上,import ... require
?和?export =
?都是 ts 為了兼容 AMD 規(guī)范和 commonjs 規(guī)范而創(chuàng)立的新語法,由于并不常用也不推薦使用,所以這里就不詳細(xì)介紹了,感興趣的可以看官方文檔。
由于很多第三方庫是 commonjs 規(guī)范的,所以聲明文件也就不得不用到?export =
?這種語法了。但是還是需要再強(qiáng)調(diào)下,相比與?export =
,我們更推薦使用 ES6 標(biāo)準(zhǔn)的?export default
?和?export
。
UMD 庫§
既可以通過?<script>
?標(biāo)簽引入,又可以通過?import
?導(dǎo)入的庫,稱為 UMD 庫。相比于 npm 包的類型聲明文件,我們需要額外聲明一個(gè)全局變量,為了實(shí)現(xiàn)這種方式,ts 提供了一個(gè)新語法?export as namespace
。
export as namespace
§
一般使用?export as namespace
?時(shí),都是先有了 npm 包的聲明文件,再基于它添加一條?export as namespace
?語句,即可將聲明好的一個(gè)變量聲明為全局變量,舉例如下22:
// types/foo/index.d.tsexport as namespace foo;
export = foo;declare function foo(): string;
declare namespace foo {const bar: number;
}
當(dāng)然它也可以與?export default
?一起使用:
// types/foo/index.d.tsexport as namespace foo;
export default foo;declare function foo(): string;
declare namespace foo {const bar: number;
}
直接擴(kuò)展全局變量§
有的第三方庫擴(kuò)展了一個(gè)全局變量,可是此全局變量的類型卻沒有相應(yīng)的更新過來,就會(huì)導(dǎo)致 ts 編譯錯(cuò)誤,此時(shí)就需要擴(kuò)展全局變量的類型。比如擴(kuò)展?String
?類型23:
interface String {prependHello(): string;
}'foo'.prependHello();
通過聲明合并,使用?interface String
?即可給?String
?添加屬性或方法。
也可以使用?declare namespace
?給已有的命名空間添加類型聲明24:
// types/jquery-plugin/index.d.tsdeclare namespace JQuery {interface CustomOptions {bar: string;}
}interface JQueryStatic {foo(options: JQuery.CustomOptions): string;
}
// src/index.tsjQuery.foo({bar: ''
});
在 npm 包或 UMD 庫中擴(kuò)展全局變量§
如之前所說,對(duì)于一個(gè) npm 包或者 UMD 庫的聲明文件,只有?export
?導(dǎo)出的類型聲明才能被導(dǎo)入。所以對(duì)于 npm 包或 UMD 庫,如果導(dǎo)入此庫之后會(huì)擴(kuò)展全局變量,則需要使用另一種語法在聲明文件中擴(kuò)展全局變量的類型,那就是?declare global
。
declare global
§
使用?declare global
?可以在 npm 包或者 UMD 庫的聲明文件中擴(kuò)展全局變量的類型25:
// types/foo/index.d.tsdeclare global {interface String {prependHello(): string;}
}export {};
// src/index.ts'bar'.prependHello();
注意即使此聲明文件不需要導(dǎo)出任何東西,仍然需要導(dǎo)出一個(gè)空對(duì)象,用來告訴編譯器這是一個(gè)模塊的聲明文件,而不是一個(gè)全局變量的聲明文件。
模塊插件§
有時(shí)通過?import
?導(dǎo)入一個(gè)模塊插件,可以改變另一個(gè)原有模塊的結(jié)構(gòu)。此時(shí)如果原有模塊已經(jīng)有了類型聲明文件,而插件模塊沒有類型聲明文件,就會(huì)導(dǎo)致類型不完整,缺少插件部分的類型。ts 提供了一個(gè)語法?declare module
,它可以用來擴(kuò)展原有模塊的類型。
declare module
§
如果是需要擴(kuò)展原有模塊的話,需要在類型聲明文件中先引用原有模塊,再使用?declare module
?擴(kuò)展原有模塊26:
// types/moment-plugin/index.d.tsimport * as moment from 'moment';declare module 'moment' {export function foo(): moment.CalendarKey;
}
// src/index.tsimport * as moment from 'moment';
import 'moment-plugin';moment.foo();
declare module
?也可用于在一個(gè)文件中一次性聲明多個(gè)模塊的類型27:
// types/foo-bar.d.tsdeclare module 'foo' {export interface Foo {foo: string;}
}declare module 'bar' {export function bar(): string;
}
// src/index.tsimport { Foo } from 'foo';
import * as bar from 'bar';let f: Foo;
bar.bar();
聲明文件中的依賴§
一個(gè)聲明文件有時(shí)會(huì)依賴另一個(gè)聲明文件中的類型,比如在前面的?declare module
?的例子中,我們就在聲明文件中導(dǎo)入了?moment
,并且使用了?moment.CalendarKey
?這個(gè)類型:
// types/moment-plugin/index.d.tsimport * as moment from 'moment';declare module 'moment' {export function foo(): moment.CalendarKey;
}
除了可以在聲明文件中通過?import
?導(dǎo)入另一個(gè)聲明文件中的類型之外,還有一個(gè)語法也可以用來導(dǎo)入另一個(gè)聲明文件,那就是三斜線指令。
三斜線指令§
與?namespace
?類似,三斜線指令也是 ts 在早期版本中為了描述模塊之間的依賴關(guān)系而創(chuàng)造的語法。隨著 ES6 的廣泛應(yīng)用,現(xiàn)在已經(jīng)不建議再使用 ts 中的三斜線指令來聲明模塊之間的依賴關(guān)系了。
但是在聲明文件中,它還是有一定的用武之地。
類似于聲明文件中的?import
,它可以用來導(dǎo)入另一個(gè)聲明文件。與?import
?的區(qū)別是,當(dāng)且僅當(dāng)在以下幾個(gè)場(chǎng)景下,我們才需要使用三斜線指令替代?import
:
- 當(dāng)我們?cè)?strong>書寫一個(gè)全局變量的聲明文件時(shí)
- 當(dāng)我們需要依賴一個(gè)全局變量的聲明文件時(shí)
書寫一個(gè)全局變量的聲明文件§
這些場(chǎng)景聽上去很拗口,但實(shí)際上很好理解——在全局變量的聲明文件中,是不允許出現(xiàn)?import
,?export
?關(guān)鍵字的。一旦出現(xiàn)了,那么他就會(huì)被視為一個(gè) npm 包或 UMD 庫,就不再是全局變量的聲明文件了。故當(dāng)我們?cè)跁鴮懸粋€(gè)全局變量的聲明文件時(shí),如果需要引用另一個(gè)庫的類型,那么就必須用三斜線指令了28:
// types/jquery-plugin/index.d.ts/// <reference types="jquery" />declare function foo(options: JQuery.AjaxSettings): string;
// src/index.tsfoo({});
三斜線指令的語法如上,///
?后面使用 xml 的格式添加了對(duì)?jquery
?類型的依賴,這樣就可以在聲明文件中使用?JQuery.AjaxSettings
?類型了。
注意,三斜線指令必須放在文件的最頂端,三斜線指令的前面只允許出現(xiàn)單行或多行注釋。
依賴一個(gè)全局變量的聲明文件§
在另一個(gè)場(chǎng)景下,當(dāng)我們需要依賴一個(gè)全局變量的聲明文件時(shí),由于全局變量不支持通過?import
?導(dǎo)入,當(dāng)然也就必須使用三斜線指令來引入了29:
// types/node-plugin/index.d.ts/// <reference types="node" />export function foo(p: NodeJS.Process): string;
// src/index.tsimport { foo } from 'node-plugin';foo(global.process);
在上面的例子中,我們通過三斜線指引入了?node
?的類型,然后在聲明文件中使用了?NodeJS.Process
?這個(gè)類型。最后在使用到?foo
?的時(shí)候,傳入了?node
?中的全局變量?process
。
由于引入的?node
?中的類型都是全局變量的類型,它們是沒有辦法通過?import
?來導(dǎo)入的,所以這種場(chǎng)景下也只能通過三斜線指令來引入了。
以上兩種使用場(chǎng)景下,都是由于需要書寫或需要依賴全局變量的聲明文件,所以必須使用三斜線指令。在其他的一些不是必要使用三斜線指令的情況下,就都需要使用?import
?來導(dǎo)入。
拆分聲明文件§
當(dāng)我們的全局變量的聲明文件太大時(shí),可以通過拆分為多個(gè)文件,然后在一個(gè)入口文件中將它們一一引入,來提高代碼的可維護(hù)性。比如?jQuery
?的聲明文件就是這樣的:
// node_modules/@types/jquery/index.d.ts/// <reference types="sizzle" />
/// <reference path="JQueryStatic.d.ts" />
/// <reference path="JQuery.d.ts" />
/// <reference path="misc.d.ts" />
/// <reference path="legacy.d.ts" />export = jQuery;
其中用到了?types
?和?path
?兩種不同的指令。它們的區(qū)別是:types
?用于聲明對(duì)另一個(gè)庫的依賴,而?path
?用于聲明對(duì)另一個(gè)文件的依賴。
上例中,sizzle
?是與?jquery
?平行的另一個(gè)庫,所以需要使用?types="sizzle"
?來聲明對(duì)它的依賴。而其他的三斜線指令就是將?jquery
?的聲明拆分到不同的文件中了,然后在這個(gè)入口文件中使用?path="foo"
?將它們一一引入。
其他三斜線指令§
除了這兩種三斜線指令之外,還有其他的三斜線指令,比如?/// <reference no-default-lib="true"/>
,?/// <amd-module />
?等,但它們都是廢棄的語法,故這里就不介紹了,詳情可見官網(wǎng)。
自動(dòng)生成聲明文件§
如果庫的源碼本身就是由 ts 寫的,那么在使用?tsc
?腳本將 ts 編譯為 js 的時(shí)候,添加?declaration
?選項(xiàng),就可以同時(shí)也生成?.d.ts
?聲明文件了。
我們可以在命令行中添加?--declaration
(簡(jiǎn)寫?-d
),或者在?tsconfig.json
?中添加?declaration
?選項(xiàng)。這里以?tsconfig.json
?為例:
{"compilerOptions": {"module": "commonjs","outDir": "lib","declaration": true,}
}
上例中我們添加了?outDir
?選項(xiàng),將 ts 文件的編譯結(jié)果輸出到?lib
?目錄下,然后添加了?declaration
?選項(xiàng),設(shè)置為?true
,表示將會(huì)由 ts 文件自動(dòng)生成?.d.ts
?聲明文件,也會(huì)輸出到?lib
?目錄下。
運(yùn)行?tsc
?之后,目錄結(jié)構(gòu)如下30:
/path/to/project
├── lib
| ├── bar
| | ├── index.d.ts
| | └── index.js
| ├── index.d.ts
| └── index.js
├── src
| ├── bar
| | └── index.ts
| └── index.ts
├── package.json
└── tsconfig.json
在這個(gè)例子中,src
?目錄下有兩個(gè) ts 文件,分別是?src/index.ts
?和?src/bar/index.ts
,它們被編譯到?lib
?目錄下的同時(shí),也會(huì)生成對(duì)應(yīng)的兩個(gè)聲明文件?lib/index.d.ts
?和?lib/bar/index.d.ts
。它們的內(nèi)容分別是:
// src/index.tsexport * from './bar';export default function foo() {return 'foo';
}
// src/bar/index.tsexport function bar() {return 'bar';
}
// lib/index.d.tsexport * from './bar';
export default function foo(): string;
// lib/bar/index.d.tsexport declare function bar(): string;
可見,自動(dòng)生成的聲明文件基本保持了源碼的結(jié)構(gòu),而將具體實(shí)現(xiàn)去掉了,生成了對(duì)應(yīng)的類型聲明。
使用?tsc
?自動(dòng)生成聲明文件時(shí),每個(gè) ts 文件都會(huì)對(duì)應(yīng)一個(gè)?.d.ts
?聲明文件。這樣的好處是,使用方不僅可以在使用?import foo from 'foo'
?導(dǎo)入默認(rèn)的模塊時(shí)獲得類型提示,還可以在使用?import bar from 'foo/lib/bar'
?導(dǎo)入一個(gè)子模塊時(shí),也獲得對(duì)應(yīng)的類型提示。
除了?declaration
?選項(xiàng)之外,還有幾個(gè)選項(xiàng)也與自動(dòng)生成聲明文件有關(guān),這里只簡(jiǎn)單列舉出來,不做詳細(xì)演示了:
declarationDir
?設(shè)置生成?.d.ts
?文件的目錄declarationMap
?對(duì)每個(gè)?.d.ts
?文件,都生成對(duì)應(yīng)的?.d.ts.map
(sourcemap)文件emitDeclarationOnly
?僅生成?.d.ts
?文件,不生成?.js
?文件
發(fā)布聲明文件§
當(dāng)我們?yōu)橐粋€(gè)庫寫好了聲明文件之后,下一步就是將它發(fā)布出去了。
此時(shí)有兩種方案:
- 將聲明文件和源碼放在一起
- 將聲明文件發(fā)布到?
@types
?下
這兩種方案中優(yōu)先選擇第一種方案。保持聲明文件與源碼在一起,使用時(shí)就不需要額外增加單獨(dú)的聲明文件庫的依賴了,而且也能保證聲明文件的版本與源碼的版本保持一致。
僅當(dāng)我們?cè)诮o別人的倉庫添加類型聲明文件,但原作者不愿意合并 pull request 時(shí),才需要使用第二種方案,將聲明文件發(fā)布到?@types
?下。
將聲明文件和源碼放在一起§
如果聲明文件是通過?tsc
?自動(dòng)生成的,那么無需做任何其他配置,只需要把編譯好的文件也發(fā)布到 npm 上,使用方就可以獲取到類型提示了。
如果是手動(dòng)寫的聲明文件,那么需要滿足以下條件之一,才能被正確的識(shí)別:
- 給?
package.json
?中的?types
?或?typings
?字段指定一個(gè)類型聲明文件地址 - 在項(xiàng)目根目錄下,編寫一個(gè)?
index.d.ts
?文件 - 針對(duì)入口文件(
package.json
?中的?main
?字段指定的入口文件),編寫一個(gè)同名不同后綴的?.d.ts
?文件
第一種方式是給?package.json
?中的?types
?或?typings
?字段指定一個(gè)類型聲明文件地址。比如:
{"name": "foo","version": "1.0.0","main": "lib/index.js","types": "foo.d.ts",
}
指定了?types
?為?foo.d.ts
?之后,導(dǎo)入此庫的時(shí)候,就會(huì)去找?foo.d.ts
?作為此庫的類型聲明文件了。
typings
?與?types
?一樣,只是另一種寫法。
如果沒有指定?types
?或?typings
,那么就會(huì)在根目錄下尋找?index.d.ts
?文件,將它視為此庫的類型聲明文件。
如果沒有找到?index.d.ts
?文件,那么就會(huì)尋找入口文件(package.json
?中的?main
?字段指定的入口文件)是否存在對(duì)應(yīng)同名不同后綴的?.d.ts
?文件。
比如?package.json
?是這樣時(shí):
{"name": "foo","version": "1.0.0","main": "lib/index.js"
}
就會(huì)先識(shí)別?package.json
?中是否存在?types
?或?typings
?字段。發(fā)現(xiàn)不存在,那么就會(huì)尋找是否存在?index.d.ts
?文件。如果還是不存在,那么就會(huì)尋找是否存在?lib/index.d.ts
?文件。假如說連?lib/index.d.ts
?都不存在的話,就會(huì)被認(rèn)為是一個(gè)沒有提供類型聲明文件的庫了。
有的庫為了支持導(dǎo)入子模塊,比如?import bar from 'foo/lib/bar'
,就需要額外再編寫一個(gè)類型聲明文件?lib/bar.d.ts
?或者?lib/bar/index.d.ts
,這與自動(dòng)生成聲明文件類似,一個(gè)庫中同時(shí)包含了多個(gè)類型聲明文件。
將聲明文件發(fā)布到?@types
?下§
如果我們是在給別人的倉庫添加類型聲明文件,但原作者不愿意合并 pull request,那么就需要將聲明文件發(fā)布到?@types
?下。
與普通的 npm 模塊不同,@types
?是統(tǒng)一由?DefinitelyTyped?管理的。要將聲明文件發(fā)布到?@types
?下,就需要給?DefinitelyTyped?創(chuàng)建一個(gè) pull-request,其中包含了類型聲明文件,測(cè)試代碼,以及?tsconfig.json
?等。
pull-request 需要符合它們的規(guī)范,并且通過測(cè)試,才能被合并,稍后就會(huì)被自動(dòng)發(fā)布到?@types
?下。
在?DefinitelyTyped?中創(chuàng)建一個(gè)新的類型聲明,需要用到一些工具,DefinitelyTyped?的文檔中已經(jīng)有了詳細(xì)的介紹,這里就不贅述了,以官方文檔為準(zhǔn)。
參考文檔
簡(jiǎn)介 · TypeScript 入門教程