<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                # 聲明文件 當使用第三方庫時,我們需要引用它的聲明文件,才能獲得對應的代碼補全、接口提示等功能。 ## 新語法索引 由于本章涉及大量新語法,故在本章開頭列出新語法的索引,方便大家在使用這些新語法時能快速查找到對應的講解: - [`declare var`](#declare-var) 聲明全局變量 - [`declare function`](#declare-function) 聲明全局方法 - [`declare class`](#declare-class) 聲明全局類 - [`declare enum`](#declare-enum) 聲明全局枚舉類型 - [`declare namespace`](#declare-namespace) 聲明(含有子屬性的)全局對象 - [`interface` 和 `type`](#interface-he-type) 聲明全局類型 - [`export`](#export) 導出變量 - [`export namespace`](#export-namespace) 導出(含有子屬性的)對象 - [`export default`](#export-default) ES6 默認導出 - [`export =`](#export-1) commonjs 導出模塊 - [`export as namespace`](#export-as-namespace) UMD 庫聲明全局變量 - [`declare global`](#declare-global) 擴展全局變量 - [`declare module`](#declare-module) 擴展模塊 - [`/// <reference />`](#san-xie-xian-zhi-ling) 三斜線指令 ## 什么是聲明語句 假如我們想使用第三方庫 jQuery,一種常見的方式是在 html 中通過 `<script>` 標簽引入 jQuery,然后就可以使用全局變量 `$` 或 `jQuery` 了。 我們通常這樣獲取一個 `id` 是 `foo` 的元素: ```js $('#foo'); // or jQuery('#foo'); ``` 但是在 ts 中,編譯器并不知道 `$` 或 `jQuery` 是什么東西[<sup>1</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/01-jquery): ```ts jQuery('#foo'); // ERROR: Cannot find name 'jQuery'. ``` 這時,我們需要使用 `declare var` 來定義它的類型[<sup>2</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/02-declare-var): ```ts declare var jQuery: (selector: string) => any; jQuery('#foo'); ``` 上例中,`declare var` 并沒有真的定義一個變量,只是定義了全局變量 `jQuery` 的類型,僅僅會用于編譯時的檢查,在編譯結果中會被刪除。它編譯結果是: ```js jQuery('#foo'); ``` 除了 `declare var` 之外,還有其他很多種聲明語句,將會在后面詳細介紹。 ## 什么是聲明文件 通常我們會把聲明語句放到一個單獨的文件(`jQuery.d.ts`)中,這就是聲明文件[<sup>3</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/03-jquery-d-ts): ```ts // src/jQuery.d.ts declare var jQuery: (selector: string) => any; ``` ```ts // src/index.ts jQuery('#foo'); ``` 聲明文件必需以 `.d.ts` 為后綴。 一般來說,ts 會解析項目中所有的 `*.ts` 文件,當然也包含以 `.d.ts` 結尾的文件。所以當我們將 `jQuery.d.ts` 放到項目中時,其他所有 `*.ts` 文件就都可以獲得 `jQuery` 的類型定義了。 ```plain /path/to/project ├── src | ├── index.ts | └── jQuery.d.ts └── tsconfig.json ``` 假如仍然無法解析,那么可以檢查下 `tsconfig.json` 中的 `files`、`include` 和 `exclude` 配置,確保其包含了 `jQuery.d.ts` 文件。 這里只演示了全局變量這種模式的聲明文件,假如是通過模塊導入的方式使用第三方庫的話,那么引入聲明文件又是另一種方式了,將會在后面詳細介紹。 ### 第三方聲明文件 當然,jQuery 的聲明文件不需要我們定義了,社區已經幫我們定義好了:[jQuery in DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/jquery/index.d.ts)。 我們可以直接下載下來使用,但是更推薦的是使用 `@types` 統一管理第三方庫的聲明文件。 `@types` 的使用方式很簡單,直接用 npm 安裝對應的聲明模塊即可,以 jQuery 舉例: ```bash npm install @types/jquery --save-dev ``` 可以在[這個頁面](https://microsoft.github.io/TypeSearch/)搜索你需要的聲明文件。 ## 書寫聲明文件 當一個第三方庫沒有提供聲明文件時,我們就需要自己書寫聲明文件了。前面只介紹了最簡單的聲明文件內容,而真正書寫一個聲明文件并不是一件簡單的事,以下會詳細介紹如何書寫聲明文件。 在不同的場景下,聲明文件的內容和使用方式會有所區別。 庫的使用場景主要有以下幾種: - [全局變量](#quan-ju-bian-liang):通過 `<script>` 標簽引入第三方庫,注入全局變量 - [npm 包](#npm-bao):通過 `import foo from 'foo'` 導入,符合 ES6 模塊規范 - [UMD 庫](#umd-ku):既可以通過 `<script>` 標簽引入,又可以通過 `import` 導入 - [直接擴展全局變量](#zhi-jie-kuo-zhan-quan-ju-bian-liang):通過 `<script>` 標簽引入后,改變一個全局變量的結構 - [在 npm 包或 UMD 庫中擴展全局變量](#zai-npm-bao-huo-umd-ku-zhong-kuo-zhan-quan-ju-bian-liang):引用 npm 包或 UMD 庫后,改變一個全局變量的結構 - [模塊插件](#mo-kuai-cha-jian):通過 `<script>` 或 `import` 導入后,改變另一個模塊的結構 ### 全局變量 全局變量是最簡單的一種場景,之前舉的例子就是通過 `<script>` 標簽引入 jQuery,注入全局變量 `$` 和 `jQuery`。 使用全局變量的聲明文件時,如果是以 `npm install @types/xxx --save-dev` 安裝的,則不需要任何配置。如果是將聲明文件直接存放于當前項目中,則建議和其他源碼一起放到 `src` 目錄下(或者對應的源碼目錄下): ```plain /path/to/project ├── src | ├── index.ts | └── jQuery.d.ts └── tsconfig.json ``` 如果沒有生效,可以檢查下 `tsconfig.json` 中的 `files`、`include` 和 `exclude` 配置,確保其包含了 `jQuery.d.ts` 文件。 全局變量的聲明文件主要有以下幾種語法: - [`declare var`](#declare-var) 聲明全局變量 - [`declare function`](#declare-function) 聲明全局方法 - [`declare class`](#declare-class) 聲明全局類 - [`declare enum`](#declare-enum) 聲明全局枚舉類型 - [`declare namespace`](#declare-namespace) 聲明(含有子屬性的)全局對象 - [`interface` 和 `type`](#interface-he-type) 聲明全局類型 #### `declare var` 在所有的聲明語句中,`declare var` 是最簡單的,如之前所學,它能夠用來定義一個全局變量的類型。與其類似的,還有 `declare let` 和 `declare const`,使用 `let` 與使用 `var` 沒有什么區別: ```ts // src/jQuery.d.ts declare let jQuery: (selector: string) => any; ``` ```ts // src/index.ts jQuery('#foo'); // 使用 declare let 定義的 jQuery 類型,允許修改這個全局變量 jQuery = function(selector) { return document.querySelector(selector); }; ``` 而當我們使用 `const` 定義時,表示此時的全局變量是一個常量,不允許再去修改它的值了[<sup>4</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/04-declare-const-jquery): ```ts // src/jQuery.d.ts declare const jQuery: (selector: string) => any; jQuery('#foo'); // 使用 declare const 定義的 jQuery 類型,禁止修改這個全局變量 jQuery = function(selector) { return document.querySelector(selector); }; // ERROR: Cannot assign to 'jQuery' because it is a constant or a read-only property. ``` 一般來說,全局變量都是禁止修改的常量,所以大部分情況都應該使用 `const` 而不是 `var` 或 `let`。 需要注意的是,聲明語句中只能定義類型,切勿在聲明語句中定義具體的實現[<sup>5</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/05-declare-jquery-value): ```ts declare const jQuery = function(selector) { return document.querySelector(selector); }; // ERROR: An implementation cannot be declared in ambient contexts. ``` #### `declare function` `declare function` 用來定義全局函數的類型。jQuery 其實就是一個函數,所以也可以用 `function` 來定義: ```ts // src/jQuery.d.ts declare function jQuery(selector: string): any; ``` ```ts // src/index.ts jQuery('#foo'); ``` 在函數類型的聲明語句中,函數重載也是支持的[<sup>6</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/06-declare-function): ```ts // src/jQuery.d.ts declare function jQuery(selector: string): any; declare function jQuery(domReadyCallback: () => any): any; ``` ```ts // src/index.ts jQuery('#foo'); jQuery(function() { alert('Dom Ready!'); }); ``` #### `declare class` 當全局變量是一個類的時候,我們用 `declare class` 來定義它的類型[<sup>7</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/07-declare-class): ```ts // src/Animal.d.ts declare class Animal { name: string; constructor(name: string); sayHi(): string; } ``` ```ts // src/index.ts let cat = new Animal('Tom'); ``` 同樣的,`declare class` 語句也只能用來定義類型,不能用來定義具體的實現,比如定義 `sayHi` 方法的具體實現則會報錯: ```ts // src/Animal.d.ts declare 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),舉例如下[<sup>8</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/08-declare-enum): ```ts // src/Directions.d.ts declare enum Directions { Up, Down, Left, Right } ``` ```ts // src/index.ts let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]; ``` 與其他全局變量的類型聲明一致,`declare enum` 僅用來定義類型,而不是具體的值。 `Directions.d.ts` 僅僅會用于編譯時的檢查,聲明文件里的內容在編譯結果中會被刪除。它編譯結果是: ```js var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]; ``` 其中 `Directions` 是由第三方庫定義好的全局變量。 #### `declare namespace` `namespace` 是 ts 早期時為了解決模塊化而創造的關鍵字,中文稱為命名空間。 由于歷史遺留原因,在早期還沒有 ES6 的時候,ts 提供了一種模塊化方案,使用 `module` 關鍵字表示內部模塊。但由于后來 ES6 也使用了 `module` 關鍵字,ts 為了兼容 ES6,使用 `namespace` 替代了自己的 `module`,更名為命名空間。 隨著 ES6 的廣泛應用,現在已經不建議再使用 ts 中的 `namespace`,而推薦使用 ES6 的模塊化方案了,故我們不再需要學習 `namespace` 的使用了。 `namespace` 被淘汰了,但是在聲明文件中,`declare namespace` 還是比較常用的,它用來表示全局變量是一個對象,包含很多子屬性。 比如 `jQuery` 是一個全局變量,它是一個對象,提供了一個 `jQuery.ajax` 方法可以調用,那么我們就應該使用 `declare namespace jQuery` 來聲明這個擁有多個子屬性的全局變量。 ```ts // src/jQuery.d.ts declare namespace jQuery { function ajax(url: string, settings?: any): void; } ``` ```ts // src/index.ts jQuery.ajax('/api/get_something'); ``` 注意,在 `declare namespace` 內部,我們直接使用 `function ajax` 來聲明函數,而不是使用 `declare function ajax`。類似的,也可以使用 `const`, `class`, `enum` 等語句[<sup>9</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/09-declare-namespace): ```ts // src/jQuery.d.ts declare namespace jQuery { function ajax(url: string, settings?: any): void; const version: number; class Event { blur(eventType: EventType): void } enum EventType { CustomClick } } ``` ```ts // src/index.ts jQuery.ajax('/api/get_something'); console.log(jQuery.version); const e = new jQuery.Event(); e.blur(jQuery.EventType.CustomClick); ``` ##### 嵌套的命名空間 如果對象擁有深層的層級,則需要用嵌套的 `namespace` 來聲明深層的屬性的類型[<sup>10</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/10-declare-namespace-nesting): ```ts // src/jQuery.d.ts declare namespace jQuery { function ajax(url: string, settings?: any): void; namespace fn { function extend(object: any): void; } } ``` ```ts // src/index.ts jQuery.ajax('/api/get_something'); jQuery.fn.extend({ check: function() { return this.each(function() { this.checked = true; }); } }); ``` 假如 `jQuery` 下僅有 `fn` 這一個屬性(沒有 `ajax` 等其他屬性或方法),則可以不需要嵌套 `namespace`[<sup>11</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/11-declare-namespace-dot): ```ts // src/jQuery.d.ts declare namespace jQuery.fn { function extend(object: any): void; } ``` ```ts // src/index.ts jQuery.fn.extend({ check: function() { return this.each(function() { this.checked = true; }); } }); ``` #### `interface` 和 `type` 除了全局變量之外,可能有一些類型我們也希望能暴露出來。在類型聲明文件中,我們可以直接使用 `interface` 或 `type` 來聲明一個全局的接口或類型[<sup>12</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/12-interface): ```ts // src/jQuery.d.ts interface AjaxSettings { method?: 'GET' | 'POST' data?: any; } declare namespace jQuery { function ajax(url: string, settings?: AjaxSettings): void; } ``` 這樣的話,在其他文件中也可以使用這個接口或類型了: ```ts // src/index.ts let settings: AjaxSettings = { method: 'POST', data: { name: 'foo' } }; jQuery.ajax('/api/post_something', settings); ``` `type` 與 `interface` 類似,不再贅述。 ##### 防止命名沖突 暴露在最外層的 `interface` 或 `type` 會作為全局類型作用于整個項目中,我們應該盡可能的減少全局變量或全局類型的數量。故最好將他們放到 `namespace` 下[<sup>13</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/13-avoid-name-conflict): ```ts // src/jQuery.d.ts declare namespace jQuery { interface AjaxSettings { method?: 'GET' | 'POST' data?: any; } function ajax(url: string, settings?: AjaxSettings): void; } ``` 注意,在使用這個 `interface` 的時候,也應該加上 `jQuery` 前綴: ```ts // src/index.ts let settings: jQuery.AjaxSettings = { method: 'POST', data: { name: 'foo' } }; jQuery.ajax('/api/post_something', settings); ``` #### 聲明合并 假如 jQuery 既是一個函數,可以直接被調用 `jQuery('#foo')`,又是一個對象,擁有子屬性 `jQuery.ajax()`(事實確實如此),那么我們可以組合多個聲明語句,它們會不沖突的合并起來[<sup>14</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/14-declaration-merging): ```ts // src/jQuery.d.ts declare function jQuery(selector: string): any; declare namespace jQuery { function ajax(url: string, settings?: any): void; } ``` ```ts // src/index.ts jQuery('#foo'); jQuery.ajax('/api/get_something'); ``` 關于聲明合并的更多用法,可以查看[聲明合并](../advanced/declaration-merging.md)章節。 ### npm 包 一般我們通過 `import foo from 'foo'` 導入一個 npm 包,這是符合 ES6 模塊規范的。 在我們嘗試給一個 npm 包創建聲明文件之前,需要先看看它的聲明文件是否已經存在。一般來說,npm 包的聲明文件可能存在于兩個地方: 1. 與該 npm 包綁定在一起。判斷依據是 `package.json` 中有 `types` 字段,或者有一個 `index.d.ts` 聲明文件。這種模式不需要額外安裝其他包,是最為推薦的,所以以后我們自己創建 npm 包的時候,最好也將聲明文件與 npm 包綁定在一起。 2. 發布到 `@types` 里。我們只需要嘗試安裝一下對應的 `@types` 包就知道是否存在該聲明文件,安裝命令是 `npm install @types/foo --save-dev`。這種模式一般是由于 npm 包的維護者沒有提供聲明文件,所以只能由其他人將聲明文件發布到 `@types` 里了。 假如以上兩種方式都沒有找到對應的聲明文件,那么我們就需要自己為它寫聲明文件了。由于是通過 `import` 語句導入的模塊,所以聲明文件存放的位置也有所約束,一般有兩種方案: 1. 創建一個 `node_modules/@types/foo/index.d.ts` 文件,存放 `foo` 模塊的聲明文件。這種方式不需要額外的配置,但是 `node_modules` 目錄不穩定,代碼也沒有被保存到倉庫中,無法回溯版本,有不小心被刪除的風險,故不太建議用這種方案,一般只用作臨時測試。 2. 創建一個 `types` 目錄,專門用來管理自己寫的聲明文件,將 `foo` 的聲明文件放到 `types/foo/index.d.ts` 中。這種方式需要配置下 `tsconfig.json` 中的 `paths` 和 `baseUrl` 字段。 目錄結構: ```plain /path/to/project ├── src | └── index.ts ├── types | └── foo | └── index.d.ts └── tsconfig.json ``` `tsconfig.json` 內容: ```json { "compilerOptions": { "module": "commonjs", "baseUrl": "./", "paths": { "*": ["types/*"] } } } ``` 如此配置之后,通過 `import` 導入 `foo` 的時候,也會去 `types` 目錄下尋找對應的模塊的聲明文件了。 注意 `module` 配置可以有很多種選項,不同的選項會影響模塊的導入導出模式。這里我們使用了 `commonjs` 這個最常用的選項,后面的教程也都默認使用的這個選項。 不管采用了以上兩種方式中的哪一種,我都**強烈建議**大家將書寫好的聲明文件(通過給第三方庫發 pull request,或者直接提交到 `@types` 里)發布到開源社區中,享受了這么多社區的優秀的資源,就應該在力所能及的時候給出一些回饋。只有所有人都參與進來,才能讓 ts 社區更加繁榮。 npm 包的聲明文件主要有以下幾種語法: - [`export`](#export) 導出變量 - [`export namespace`](#export-namespace) 導出(含有子屬性的)對象 - [`export default`](#export-default) ES6 默認導出 - [`export =`](#export-1) commonjs 導出模塊 #### `export` npm 包的聲明文件與全局變量的聲明文件有很大區別。在 npm 包的聲明文件中,使用 `declare` 不再會聲明一個全局變量,而只會在當前文件中聲明一個局部變量。只有在聲明文件中使用 `export` 導出,然后在使用方 `import` 導入后,才會應用到這些類型聲明。 `export` 的語法與普通的 ts 中的語法類似,區別僅在于聲明文件中禁止定義具體的實現[<sup>15</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/15-export): ```ts // types/foo/index.d.ts export 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; } ``` 對應的導入和使用模塊應該是這樣: ```ts // src/index.ts import { 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` 先聲明多個變量,最后再用 `export` 一次性導出。上例的聲明文件可以等價的改寫為[<sup>16</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/16-declare-and-export): ```ts // types/foo/index.d.ts declare 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` 用來導出一個擁有子屬性的對象[<sup>17</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/17-export-namespace): ```ts // types/foo/index.d.ts export namespace foo { const name: string; namespace bar { function baz(): string; } } ``` ```ts // src/index.ts import { foo } from 'foo'; console.log(foo.name); foo.bar.baz(); ``` #### `export default` 在 ES6 模塊系統中,使用 `export default` 可以導出一個默認值,使用方可以用 `import foo from 'foo'` 而不是 `import { foo } from 'foo'` 來導入這個默認值。 在類型聲明文件中,`export default` 用來導出默認值的類型[<sup>18</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/18-export-default): ```ts // types/foo/index.d.ts export default function foo(): string; ``` ```ts // src/index.ts import foo from 'foo'; foo(); ``` 注意,只有 `function`、`class` 和 `interface` 可以直接默認導出,其他的變量需要先定義出來,再默認導出[<sup>19</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/19-export-default-enum-error): ```ts // types/foo/index.d.ts export default enum Directions { // ERROR: Expression expected. Up, Down, Left, Right } ``` 上例中 `export default enum` 是錯誤的語法,需要使用 `declare enum` 定義出來,然后使用 `export default` 導出: ```ts // types/foo/index.d.ts declare enum Directions { Up, Down, Left, Right } export default Directions; ``` 針對這種默認導出,我們一般會將導出語句放在整個聲明文件的最前面[<sup>20</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/20-export-default-enum): ```ts // types/foo/index.d.ts export default Directions; declare enum Directions { Up, Down, Left, Right } ``` #### `export =` 在 commonjs 規范中,我們用以下方式來導出一個模塊: ```js // 整體導出 module.exports = foo; // 單個導出 exports.bar = bar; ``` 在 ts 中,針對這種模塊導出,有多種方式可以導入,第一種方式是 `const ... = require`: ```js // 整體導入 const foo = require('foo'); // 單個導入 const bar = require('foo').bar; ``` 第二種方式是 `import ... from`,注意針對整體導出,需要使用 `import * as` 來導入: ```ts // 整體導入 import * as foo from 'foo'; // 單個導入 import { bar } from 'foo'; ``` 第三種方式是 `import ... require`,這也是 ts 官方推薦的方式: ```ts // 整體導入 import foo = require('foo'); // 單個導入 import bar = foo.bar; ``` 對于這種使用 commonjs 規范的庫,假如要為它寫類型聲明文件的話,就需要使用到 `export =` 這種語法了[<sup>21</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/21-export-equal): ```ts // types/foo/index.d.ts export = foo; declare function foo(): string; declare namespace foo { const bar: number; } ``` 需要注意的是,上例中使用了 `export =` 之后,就不能再單個導出 `export { bar }` 了。所以我們通過聲明合并,使用 `declare namespace foo` 來將 `bar` 合并到 `foo` 里。 準確地講,`export =` 不僅可以用在聲明文件中,也可以用在普通的 ts 文件中。實際上,`import ... require` 和 `export =` 都是 ts 為了兼容 AMD 規范和 commonjs 規范而創立的新語法,由于并不常用也不推薦使用,所以這里就不詳細介紹了,感興趣的可以看[官方文檔](https://www.typescriptlang.org/docs/handbook/modules.html#export--and-import--require)。 由于很多第三方庫是 commonjs 規范的,所以聲明文件也就不得不用到 `export =` 這種語法了。但是還是需要再強調下,相比與 `export =`,我們更推薦使用 ES6 標準的 `export default` 和 `export`。 ### UMD 庫 既可以通過 `<script>` 標簽引入,又可以通過 `import` 導入的庫,稱為 UMD 庫。相比于 npm 包的類型聲明文件,我們需要額外聲明一個全局變量,為了實現這種方式,ts 提供了一個新語法 `export as namespace`。 #### `export as namespace` 一般使用 `export as namespace` 時,都是先有了 npm 包的聲明文件,再基于它添加一條 `export as namespace` 語句,即可將聲明好的一個變量聲明為全局變量,舉例如下[<sup>22</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/22-export-as-namespace): ```ts // types/foo/index.d.ts export as namespace foo; export = foo; declare function foo(): string; declare namespace foo { const bar: number; } ``` 當然它也可以與 `export default` 一起使用: ```ts // types/foo/index.d.ts export as namespace foo; export default foo; declare function foo(): string; declare namespace foo { const bar: number; } ``` ### 直接擴展全局變量 有的第三方庫擴展了一個全局變量,可是此全局變量的類型卻沒有相應的更新過來,就會導致 ts 編譯錯誤,此時就需要擴展全局變量的類型。比如擴展 `String` 類型[<sup>23</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/23-merge-global-interface): ```ts interface String { prependHello(): string; } 'foo'.prependHello(); ``` 通過聲明合并,使用 `interface String` 即可給 `String` 添加屬性或方法。 也可以使用 `declare namespace` 給已有的命名空間添加類型聲明[<sup>24</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/24-merge-global-namespace): ```ts // types/jquery-plugin/index.d.ts declare namespace JQuery { interface CustomOptions { bar: string; } } interface JQueryStatic { foo(options: JQuery.CustomOptions): string; } ``` ```ts // src/index.ts jQuery.foo({ bar: '' }); ``` ### 在 npm 包或 UMD 庫中擴展全局變量 如之前所說,對于一個 npm 包或者 UMD 庫的聲明文件,只有 `export` 導出的類型聲明才能被導入。所以對于 npm 包或 UMD 庫,如果導入此庫之后會擴展全局變量,則需要使用另一種語法在聲明文件中擴展全局變量的類型,那就是 `declare global`。 #### `declare global` 使用 `declare global` 可以在 npm 包或者 UMD 庫的聲明文件中擴展全局變量的類型[<sup>25</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/25-declare-global): ```ts // types/foo/index.d.ts declare global { interface String { prependHello(): string; } } export {}; ``` ```ts // src/index.ts 'bar'.prependHello(); ``` 注意即使此聲明文件不需要導出任何東西,仍然需要導出一個空對象,用來告訴編譯器這是一個模塊的聲明文件,而不是一個全局變量的聲明文件。 ### 模塊插件 有時通過 `import` 導入一個模塊插件,可以改變另一個原有模塊的結構。此時如果原有模塊已經有了類型聲明文件,而插件模塊沒有類型聲明文件,就會導致類型不完整,缺少插件部分的類型。ts 提供了一個語法 `declare module`,它可以用來擴展原有模塊的類型。 #### `declare module` 如果是需要擴展原有模塊的話,需要在類型聲明文件中先引用原有模塊,再使用 `declare module` 擴展原有模塊[<sup>26</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/26-declare-module): ```ts // types/moment-plugin/index.d.ts import * as moment from 'moment'; declare module 'moment' { export function foo(): moment.CalendarKey; } ``` ```ts // src/index.ts import * as moment from 'moment'; import 'moment-plugin'; moment.foo(); ``` `declare module` 也可用于在一個文件中一次性聲明多個模塊的類型[<sup>27</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/27-multiple-declare-module): ```ts // types/foo-bar.d.ts declare module 'foo' { export interface Foo { foo: string; } } declare module 'bar' { export function bar(): string; } ``` ```ts // src/index.ts import { Foo } from 'foo'; import * as bar from 'bar'; let f: Foo; bar.bar(); ``` ### 聲明文件中的依賴 一個聲明文件有時會依賴另一個聲明文件中的類型,比如在前面的 `declare module` 的例子中,我們就在聲明文件中導入了 `moment`,并且使用了 `moment.CalendarKey` 這個類型: ```ts // types/moment-plugin/index.d.ts import * as moment from 'moment'; declare module 'moment' { export function foo(): moment.CalendarKey; } ``` 除了可以在聲明文件中通過 `import` 導入另一個聲明文件中的類型之外,還有一個語法也可以用來導入另一個聲明文件,那就是三斜線指令。 #### 三斜線指令 與 `namespace` 類似,三斜線指令也是 ts 在早期版本中為了描述模塊之間的依賴關系而創造的語法。隨著 ES6 的廣泛應用,現在已經不建議再使用 ts 中的三斜線指令來聲明模塊之間的依賴關系了。 但是在聲明文件中,它還是有一定的用武之地。 類似于聲明文件中的 `import`,它可以用來導入另一個聲明文件。與 `import` 的區別是,當且僅當在以下幾個場景下,我們才需要使用三斜線指令替代 `import`: - 當我們在**書寫**一個全局變量的聲明文件時 - 當我們需要**依賴**一個全局變量的聲明文件時 ##### **書寫**一個全局變量的聲明文件 這些場景聽上去很拗口,但實際上很好理解——在全局變量的聲明文件中,是不允許出現 `import`, `export` 關鍵字的。一旦出現了,那么他就會被視為一個 npm 包或 UMD 庫,就不再是全局變量的聲明文件了。故當我們在書寫一個全局變量的聲明文件時,如果需要引用另一個庫的類型,那么就必須用三斜線指令了[<sup>28</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/28-triple-slash-directives): ```ts // types/jquery-plugin/index.d.ts /// <reference types="jquery" /> declare function foo(options: JQuery.AjaxSettings): string; ``` ```ts // src/index.ts foo({}); ``` 三斜線指令的語法如上,`///` 后面使用 xml 的格式添加了對 `jquery` 類型的依賴,這樣就可以在聲明文件中使用 `JQuery.AjaxSettings` 類型了。 注意,三斜線指令必須放在文件的最頂端,三斜線指令的前面只允許出現單行或多行注釋。 ##### **依賴**一個全局變量的聲明文件 在另一個場景下,當我們需要依賴一個全局變量的聲明文件時,由于全局變量不支持通過 `import` 導入,當然也就必須使用三斜線指令來引入了[<sup>29</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/29-triple-slash-directives-global): ```ts // types/node-plugin/index.d.ts /// <reference types="node" /> export function foo(p: NodeJS.Process): string; ``` ```ts // src/index.ts import { foo } from 'node-plugin'; foo(global.process); ``` 在上面的例子中,我們通過三斜線指引入了 `node` 的類型,然后在聲明文件中使用了 `NodeJS.Process` 這個類型。最后在使用到 `foo` 的時候,傳入了 `node` 中的全局變量 `process`。 由于引入的 `node` 中的類型都是全局變量的類型,它們是沒有辦法通過 `import` 來導入的,所以這種場景下也只能通過三斜線指令來引入了。 以上兩種使用場景下,都是由于需要書寫或需要依賴全局變量的聲明文件,所以必須使用三斜線指令。在其他的一些不是必要使用三斜線指令的情況下,就都需要使用 `import` 來導入。 ##### 拆分聲明文件 當我們的全局變量的聲明文件太大時,可以通過拆分為多個文件,然后在一個入口文件中將它們一一引入,來提高代碼的可維護性。比如 `jQuery` 的聲明文件就是這樣的: ```ts // 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` 兩種不同的指令。它們的區別是:`types` 用于聲明對另一個庫的依賴,而 `path` 用于聲明對另一個文件的依賴。 上例中,`sizzle` 是與 `jquery` 平行的另一個庫,所以需要使用 `types="sizzle"` 來聲明對它的依賴。而其他的三斜線指令就是將 `jquery` 的聲明拆分到不同的文件中了,然后在這個入口文件中使用 `path="foo"` 將它們一一引入。 ##### 其他三斜線指令 除了這兩種三斜線指令之外,還有其他的三斜線指令,比如 `/// <reference no-default-lib="true"/>`, `/// <amd-module />` 等,但它們都是廢棄的語法,故這里就不介紹了,詳情可見[官網](http://www.typescriptlang.org/docs/handbook/triple-slash-directives.html)。 ### 自動生成聲明文件 如果庫的源碼本身就是由 ts 寫的,那么在使用 `tsc` 腳本將 ts 編譯為 js 的時候,添加 `declaration` 選項,就可以同時也生成 `.d.ts` 聲明文件了。 我們可以在命令行中添加 `--declaration`(簡寫 `-d`),或者在 `tsconfig.json` 中添加 `declaration` 選項。這里以 `tsconfig.json` 為例: ```json { "compilerOptions": { "module": "commonjs", "outDir": "lib", "declaration": true, } } ``` 上例中我們添加了 `outDir` 選項,將 ts 文件的編譯結果輸出到 `lib` 目錄下,然后添加了 `declaration` 選項,設置為 `true`,表示將會由 ts 文件自動生成 `.d.ts` 聲明文件,也會輸出到 `lib` 目錄下。 運行 `tsc` 之后,目錄結構如下[<sup>30</sup>](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/declaration-files/30-auto-d-ts): ```plain /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 ``` 在這個例子中,`src` 目錄下有兩個 ts 文件,分別是 `src/index.ts` 和 `src/bar/index.ts`,它們被編譯到 `lib` 目錄下的同時,也會生成對應的兩個聲明文件 `lib/index.d.ts` 和 `lib/bar/index.d.ts`。它們的內容分別是: ```ts // src/index.ts export * from './bar'; export default function foo() { return 'foo'; } ``` ```ts // src/bar/index.ts export function bar() { return 'bar'; } ``` ```ts // lib/index.d.ts export * from './bar'; export default function foo(): string; ``` ```ts // lib/bar/index.d.ts export declare function bar(): string; ``` 可見,自動生成的聲明文件基本保持了源碼的結構,而將具體實現去掉了,生成了對應的類型聲明。 使用 `tsc` 自動生成聲明文件時,每個 ts 文件都會對應一個 `.d.ts` 聲明文件。這樣的好處是,使用方不僅可以在使用 `import foo from 'foo'` 導入默認的模塊時獲得類型提示,還可以在使用 `import bar from 'foo/lib/bar'` 導入一個子模塊時,也獲得對應的類型提示。 除了 `declaration` 選項之外,還有幾個選項也與自動生成聲明文件有關,這里只簡單列舉出來,不做詳細演示了: - `declarationDir` 設置生成 `.d.ts` 文件的目錄 - `declarationMap` 對每個 `.d.ts` 文件,都生成對應的 `.d.ts.map`(sourcemap)文件 - `emitDeclarationOnly` 僅生成 `.d.ts` 文件,不生成 `.js` 文件 ## 發布聲明文件 當我們為一個庫寫好了聲明文件之后,下一步就是將它發布出去了。 此時有兩種方案: 1. 將聲明文件和源碼放在一起 2. 將聲明文件發布到 `@types` 下 這兩種方案中優先選擇第一種方案。保持聲明文件與源碼在一起,使用時就不需要額外增加單獨的聲明文件庫的依賴了,而且也能保證聲明文件的版本與源碼的版本保持一致。 僅當我們在給別人的倉庫添加類型聲明文件,但原作者不愿意合并 pull request 時,才需要使用第二種方案,將聲明文件發布到 `@types` 下。 ### 將聲明文件和源碼放在一起 如果聲明文件是通過 `tsc` 自動生成的,那么無需做任何其他配置,只需要把編譯好的文件也發布到 npm 上,使用方就可以獲取到類型提示了。 如果是手動寫的聲明文件,那么需要滿足以下條件之一,才能被正確的識別: - 給 `package.json` 中的 `types` 或 `typings` 字段指定一個類型聲明文件地址 - 在項目根目錄下,編寫一個 `index.d.ts` 文件 - 針對入口文件(`package.json` 中的 `main` 字段指定的入口文件),編寫一個同名不同后綴的 `.d.ts` 文件 第一種方式是給 `package.json` 中的 `types` 或 `typings` 字段指定一個類型聲明文件地址。比如: ```json { "name": "foo", "version": "1.0.0", "main": "lib/index.js", "types": "foo.d.ts", } ``` 指定了 `types` 為 `foo.d.ts` 之后,導入此庫的時候,就會去找 `foo.d.ts` 作為此庫的類型聲明文件了。 `typings` 與 `types` 一樣,只是另一種寫法。 如果沒有指定 `types` 或 `typings`,那么就會在根目錄下尋找 `index.d.ts` 文件,將它視為此庫的類型聲明文件。 如果沒有找到 `index.d.ts` 文件,那么就會尋找入口文件(`package.json` 中的 `main` 字段指定的入口文件)是否存在對應同名不同后綴的 `.d.ts` 文件。 比如 `package.json` 是這樣時: ```json { "name": "foo", "version": "1.0.0", "main": "lib/index.js" } ``` 就會先識別 `package.json` 中是否存在 `types` 或 `typings` 字段。發現不存在,那么就會尋找是否存在 `index.d.ts` 文件。如果還是不存在,那么就會尋找是否存在 `lib/index.d.ts` 文件。假如說連 `lib/index.d.ts` 都不存在的話,就會被認為是一個沒有提供類型聲明文件的庫了。 有的庫為了支持導入子模塊,比如 `import bar from 'foo/lib/bar'`,就需要額外再編寫一個類型聲明文件 `lib/bar.d.ts` 或者 `lib/bar/index.d.ts`,這與自動生成聲明文件類似,一個庫中同時包含了多個類型聲明文件。 ### 將聲明文件發布到 `@types` 下 如果我們是在給別人的倉庫添加類型聲明文件,但原作者不愿意合并 pull request,那么就需要將聲明文件發布到 `@types` 下。 與普通的 npm 模塊不同,`@types` 是統一由 [DefinitelyTyped][] 管理的。要將聲明文件發布到 `@types` 下,就需要給 [DefinitelyTyped][] 創建一個 pull-request,其中包含了類型聲明文件,測試代碼,以及 `tsconfig.json` 等。 pull-request 需要符合它們的規范,并且通過測試,才能被合并,稍后就會被自動發布到 `@types` 下。 在 [DefinitelyTyped][] 中創建一個新的類型聲明,需要用到一些工具,[DefinitelyTyped][] 的文檔中已經有了[詳細的介紹](https://github.com/DefinitelyTyped/DefinitelyTyped#create-a-new-package),這里就不贅述了,以官方文檔為準。 如果大家有此類需求,可以參考下筆者[提交的 pull-request](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/30336/files) 。 ## 參考 - [Writing Declaration Files](http://www.typescriptlang.org/docs/handbook/writing-declaration-files.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/declaration%20files/Introduction.html)) - [Triple-Slash Directives](http://www.typescriptlang.org/docs/handbook/triple-slash-directives.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Triple-Slash%20Directives.html)) - [typeRoots or paths](https://github.com/Microsoft/TypeScript/issues/22217#issuecomment-369783776) - [DefinitelyTyped][] --- - [上一章:類型斷言](type-assertion.md) - [下一章:內置對象](built-in-objects.md) [DefinitelyTyped]: https://github.com/DefinitelyTyped/DefinitelyTyped/
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看