本文概述了在 TypeScript 中使用模組(Modules)和名稱空間(Namespaces)組織程式碼的各種方式。我們還將探討有關如何使用名稱空間和模組的一些進階主題,並解決在 TypeScript 中使用它們時常見的一些缺陷。
有關 ES 模組的更多資訊,請參閱模組(Modules)文件。有關 TypeScript 名稱空間的更多資訊,請參閱名稱空間(Namespaces)文件。
注意:在非常早期的 TypeScript 版本中,名稱空間被稱為“內部模組”(Internal Modules),它們出現的時間早於 JavaScript 的模組系統。
使用模組
模組可以同時包含程式碼和宣告。
模組還依賴於模組載入器(如 CommonJs/Require.js)或支援 ES 模組的執行時。模組提供了更好的程式碼複用性、更強的隔離性以及更好的打包工具支援。
值得注意的是,對於 Node.js 應用程式,模組是預設選項,並且我們建議在現代程式碼中使用模組而非名稱空間。
從 ECMAScript 2015 開始,模組已成為語言的原生組成部分,並且應該得到所有相容引擎實現的支援。因此,對於新專案,模組是推薦的程式碼組織機制。
使用名稱空間
名稱空間是 TypeScript 特有的一種組織程式碼的方式。
名稱空間本質上只是全域性名稱空間中具名的 JavaScript 物件。這使得名稱空間成為一種非常易於使用的結構。與模組不同,它們可以跨越多個檔案,並可以使用 outFile 進行合併。對於 Web 應用程式,如果所有依賴項都以 <script> 標籤的形式包含在 HTML 頁面中,名稱空間是一種組織程式碼的好方法。
和所有全域性名稱空間汙染一樣,在大型應用程式中識別元件依賴關係可能會變得困難。
名稱空間與模組的常見缺陷
在本節中,我們將描述使用名稱空間和模組時常見的各種缺陷,以及如何避免它們。
對模組使用 /// <reference>
一個常見的錯誤是嘗試使用 /// <reference ... /> 語法來引用模組檔案,而不是使用 import 語句。要理解這種區別,我們首先需要了解編譯器如何根據 import 的路徑(例如 import x from "..."; 或 import x = require("..."); 等中的 ...)來定位模組的型別資訊。
編譯器會嘗試按照路徑順序查詢 .ts、.tsx,然後是 .d.ts 檔案。如果找不到特定檔案,編譯器會查詢環境模組宣告(ambient module declaration)。請記住,這些宣告需要在 .d.ts 檔案中定義。
-
myModules.d.tsts// In a .d.ts file or .ts file that is not a module:declare module "SomeModule" {export function fn(): string;} -
myOtherModule.tsts/// <reference path="myModules.d.ts" />import * as m from "SomeModule";
這裡的引用標籤允許我們定位包含環境模組宣告的宣告檔案。這就是許多 TypeScript 示例所使用的 node.d.ts 檔案的引用方式。
不必要的名稱空間
如果您正在將程式從名稱空間轉換為模組,很容易得到類似這樣的檔案:
-
shapes.tstsexport namespace Shapes {export class Triangle {/* ... */}export class Square {/* ... */}}
這裡的頂層名稱空間 Shapes 將 Triangle 和 Square 包裹起來,這是沒有必要的。這對模組的使用者來說既令人困惑又很麻煩。
-
shapeConsumer.tstsimport * as shapes from "./shapes";let t = new shapes.Shapes.Triangle(); // shapes.Shapes?
TypeScript 模組的一個關鍵特性是:兩個不同的模組絕不會將名稱貢獻給同一個作用域。因為模組的使用者決定了給它分配什麼名稱,所以沒有必要主動地將匯出的符號包裹在名稱空間中。
再次強調為什麼不應該嘗試對模組內容進行名稱空間處理:名稱空間的一般目的是提供結構的邏輯分組並防止命名衝突。因為模組檔案本身已經是一個邏輯分組,並且其頂層名稱由匯入它的程式碼定義,因此無需為匯出的物件額外新增一個模組層。
以下是修改後的示例
-
shapes.tstsexport class Triangle {/* ... */}export class Square {/* ... */} -
shapeConsumer.tstsimport * as shapes from "./shapes";let t = new shapes.Triangle();
模組的權衡
正如 JS 檔案與模組之間存在一一對應關係一樣,TypeScript 在模組原始檔與其編譯後的 JS 檔案之間也存在一一對應關係。這意味著根據您所使用的模組系統,無法將多個模組原始檔合併在一起。例如,在以 commonjs 或 umd 為目標時,不能使用 outFile 選項;但從 TypeScript 1.8 開始,在以 amd 或 system 為目標時,可以使用 outFile。