UMD
UMD 模組是指既可以作為模組使用(透過 import),也可以作為全域性變數使用(在沒有模組載入器的環境中執行時)的模組。許多流行的庫,例如 Moment.js,都是以這種方式編寫的。例如,在 Node.js 中或使用 RequireJS 時,你會寫成
tsimport moment = require("moment");console.log(moment.format());
而在普通瀏覽器環境中,你會寫成
jsconsole.log(moment.format());
識別 UMD 庫
UMD 模組會檢查模組載入環境是否存在。這是一種很容易發現的模式,看起來像這樣
js(function (root, factory) {if (typeof define === "function" && define.amd) {define(["libName"], factory);} else if (typeof module === "object" && module.exports) {module.exports = factory(require("libName"));} else {root.returnExports = factory(root.libName);}}(this, function (b) {
如果你在庫的程式碼中(尤其是在檔案頂部)看到對 typeof define、typeof window 或 typeof module 的檢查,那麼它幾乎肯定是一個 UMD 庫。
UMD 庫的文件通常也會展示一個“在 Node.js 中使用”的示例(使用 require),以及一個“在瀏覽器中使用”的示例(使用 <script> 標籤載入指令碼)。
UMD 庫示例
目前大多數流行的庫都作為 UMD 包提供。例如 jQuery、Moment.js、lodash 等等。
模板
模組有三種可用模板:module.d.ts、module-class.d.ts 和 module-function.d.ts。
如果你的模組可以像函式一樣被呼叫,請使用 module-function.d.ts
jsvar x = require("foo");// Note: calling 'x' as a functionvar y = x(42);
請務必閱讀腳註“ES6 對模組呼叫簽名的影響”
如果你的模組可以使用 new 進行構造,請使用 module-class.d.ts
jsvar x = require("bar");// Note: using 'new' operator on the imported variablevar y = new x("hello");
同樣的腳註也適用於這些模組。
如果你的模組既不能被呼叫也不能被構造,請使用 module.d.ts 檔案。
模組外掛 或 UMD 外掛
模組外掛會改變另一個模組(無論是 UMD 還是模組)的形態。例如,在 Moment.js 中,moment-range 向 moment 物件添加了一個新的 range 方法。
就編寫宣告檔案而言,無論所改變的模組是普通模組還是 UMD 模組,你編寫的程式碼都是一樣的。
模板
使用 module-plugin.d.ts 模板。
全域性外掛
全域性外掛是改變某些全域性變數形態的全域性程式碼。與修改全域性的模組一樣,這些外掛可能會引起執行時衝突。
例如,有些庫會向 Array.prototype 或 String.prototype 新增新函式。
識別全域性外掛
全域性外掛通常很容易從文件中識別出來。
你會看到類似這樣的示例
jsvar x = "hello, world";// Creates new methods on built-in typesconsole.log(x.startsWithHello());var y = [1, 2, 3];// Creates new methods on built-in typesconsole.log(y.reverseAndSort());
模板
使用 global-plugin.d.ts 模板。
修改全域性的模組
修改全域性的模組在被匯入時會更改全域性作用域中的現有值。例如,可能存在一個在匯入時向 String.prototype 新增新成員的庫。這種模式由於存在執行時衝突的可能性而有些危險,但我們仍然可以為其編寫宣告檔案。
識別修改全域性的模組
修改全域性的模組通常很容易從其文件中識別出來。總的來說,它們類似於全域性外掛,但需要呼叫 require 來啟用其效果。
你可能會看到如下文件
js// 'require' call that doesn't use its return valuevar unused = require("magic-string-time");/* or */require("magic-string-time");var x = "hello, world";// Creates new methods on built-in typesconsole.log(x.startsWithHello());var y = [1, 2, 3];// Creates new methods on built-in typesconsole.log(y.reverseAndSort());
模板
使用 global-modifying-module.d.ts 模板。
使用依賴項
你的庫可能具有多種依賴項。本節展示瞭如何將它們匯入到宣告檔案中。
對全域性庫的依賴
如果你的庫依賴於全域性庫,請使用 /// <reference types="..." /> 指令
ts/// <reference types="someLib" />function getThing(): someLib.thing;
對模組的依賴
如果你的庫依賴於模組,請使用 import 語句
tsimport * as moment from "moment";function getThing(): moment;
對 UMD 庫的依賴
來自全域性庫
如果你的全域性庫依賴於 UMD 模組,請使用 /// <reference types 指令
ts/// <reference types="moment" />function getThing(): moment;
來自模組或 UMD 庫
如果你的模組或 UMD 庫依賴於 UMD 庫,請使用 import 語句
tsimport * as someLib from "someLib";
不要使用 /// <reference 指令來宣告對 UMD 庫的依賴!
腳註
防止命名衝突
注意,在編寫全域性宣告檔案時,可以在全域性作用域中定義許多型別。我們強烈不建議這樣做,因為當一個專案中存在許多宣告檔案時,這會導致可能無法解決的命名衝突。
一個簡單的準則:只宣告由庫定義的全域性變數所名稱空間化的型別。例如,如果庫定義了全域性值 'cats',你應該寫成
tsdeclare namespace cats {interface KittySettings {}}
而不是
ts// at top-levelinterface CatsKittySettings {}
此指導原則還能確保庫可以轉換為 UMD 而不會破壞宣告檔案的使用者。
ES6 對模組外掛的影響
有些外掛會新增或修改現有模組上的頂級匯出。雖然這在 CommonJS 和其他載入器中是合法的,但 ES6 模組被認為是不可變的,這種模式將不再可行。由於 TypeScript 與載入器無關,因此沒有對此策略的編譯時強制執行,但打算轉換為 ES6 模組載入器的開發人員應注意這一點。
ES6 對模組呼叫簽名的影響
許多流行的庫(如 Express)在匯入時表現為可呼叫的函式。例如,典型的 Express 用法如下
tsimport exp = require("express");var app = exp();
在 ES6 模組載入器中,頂級物件(這裡匯入為 exp)只能具有屬性;頂級模組物件永遠不可呼叫。這裡最常見的解決方案是為可呼叫/可構造的物件定義一個 default 匯出;一些模組載入器填充(shim)會自動檢測這種情況並將頂級物件替換為 default 匯出。
庫檔案佈局
你的宣告檔案佈局應映象庫的佈局。
一個庫可以包含多個模組,例如
myLib+---- index.js+---- foo.js+---- bar+---- index.js+---- baz.js
這些可以匯入為
jsvar a = require("myLib");var b = require("myLib/foo");var c = require("myLib/bar");var d = require("myLib/bar/baz");
因此,你的宣告檔案應該是
@types/myLib+---- index.d.ts+---- foo.d.ts+---- bar+---- index.d.ts+---- baz.d.ts
ts// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]// Project: [~THE PROJECT NAME~]// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>/*~ This template shows how to write a global plugin. *//*~ Write a declaration for the original type and add new members.*~ For example, this adds a 'toBinaryString' method with overloads to*~ the built-in number type.*/interface Number {toBinaryString(opts?: MyLibrary.BinaryFormatOptions): string;toBinaryString(callback: MyLibrary.BinaryFormatCallback,opts?: MyLibrary.BinaryFormatOptions): string;}/*~ If you need to declare several types, place them inside a namespace*~ to avoid adding too many things to the global namespace.*/declare namespace MyLibrary {type BinaryFormatCallback = (n: number) => string;interface BinaryFormatOptions {prefix?: string;padding: number;}}