關於術語的說明:需要注意的是,在 TypeScript 1.5 中,術語發生了變化。“內部模組”現在被稱為“名稱空間”。“外部模組”現在簡稱為“模組”,以與 ECMAScript 2015 的術語保持一致(即
module X {等同於現在更推薦使用的namespace X {)。
本文概述了在 TypeScript 中使用名稱空間(以前稱為“內部模組”)來組織程式碼的各種方法。正如我們在術語說明中提到的,“內部模組”現在被稱為“名稱空間”。此外,在宣告內部模組時使用 module 關鍵字的地方,都可以並且應該改用 namespace 關鍵字。這避免了因使用相似術語而給新使用者帶來困惑。
入門
讓我們從本頁中將一直使用的示例程式開始。我們編寫了一組簡單的字串驗證器,就像您在網頁表單中檢查使用者輸入或檢查外部提供的資料檔案格式時所編寫的那樣。
單個檔案中的驗證器
tsinterface StringValidator {isAcceptable(s: string): boolean;}let lettersRegexp = /^[A-Za-z]+$/;let numberRegexp = /^[0-9]+$/;class LettersOnlyValidator implements StringValidator {isAcceptable(s: string) {return lettersRegexp.test(s);}}class ZipCodeValidator implements StringValidator {isAcceptable(s: string) {return s.length === 5 && numberRegexp.test(s);}}// Some samples to trylet strings = ["Hello", "98052", "101"];// Validators to uselet validators: { [s: string]: StringValidator } = {};validators["ZIP code"] = new ZipCodeValidator();validators["Letters only"] = new LettersOnlyValidator();// Show whether each string passed each validatorfor (let s of strings) {for (let name in validators) {let isMatch = validators[name].isAcceptable(s);console.log(`'${s}' ${isMatch ? "matches" : "does not match"} '${name}'.`);}}
名稱空間
當我們新增更多驗證器時,我們需要一種組織方案,以便追蹤我們的型別,而不必擔心與其他物件產生名稱衝突。與其將大量不同的名稱放入全域性名稱空間中,不如將我們的物件封裝到一個名稱空間中。
在此示例中,我們將所有與驗證器相關的實體移動到名為 Validation 的名稱空間中。由於我們希望此處的介面和類在名稱空間之外可見,因此我們在它們前面加上了 export。相反,變數 lettersRegexp 和 numberRegexp 是實現細節,因此它們保持未匯出,並且在名稱空間之外的程式碼中不可見。在檔案底部的測試程式碼中,現在當我們在名稱空間之外使用型別時,需要使用限定名稱,例如 Validation.LettersOnlyValidator。
帶名稱空間的驗證器
tsnamespace Validation {export interface StringValidator {isAcceptable(s: string): boolean;}const lettersRegexp = /^[A-Za-z]+$/;const numberRegexp = /^[0-9]+$/;export class LettersOnlyValidator implements StringValidator {isAcceptable(s: string) {return lettersRegexp.test(s);}}export class ZipCodeValidator implements StringValidator {isAcceptable(s: string) {return s.length === 5 && numberRegexp.test(s);}}}// Some samples to trylet strings = ["Hello", "98052", "101"];// Validators to uselet validators: { [s: string]: Validation.StringValidator } = {};validators["ZIP code"] = new Validation.ZipCodeValidator();validators["Letters only"] = new Validation.LettersOnlyValidator();// Show whether each string passed each validatorfor (let s of strings) {for (let name in validators) {console.log(`"${s}" - ${validators[name].isAcceptable(s) ? "matches" : "does not match"} ${name}`);}}
跨檔案拆分
隨著我們的應用程式不斷增長,我們需要將程式碼拆分到多個檔案中,以便於維護。
多檔案名稱空間
在這裡,我們將 Validation 名稱空間拆分到多個檔案中。即使檔案是分開的,它們也都可以貢獻給同一個名稱空間,並且可以像它們都定義在同一個地方一樣被使用。由於檔案之間存在依賴關係,我們將新增引用標籤來告知編譯器檔案之間的關係。我們的測試程式碼保持不變。
Validation.ts
tsnamespace Validation {export interface StringValidator {isAcceptable(s: string): boolean;}}
LettersOnlyValidator.ts
ts/// <reference path="Validation.ts" />namespace Validation {const lettersRegexp = /^[A-Za-z]+$/;export class LettersOnlyValidator implements StringValidator {isAcceptable(s: string) {return lettersRegexp.test(s);}}}
ZipCodeValidator.ts
ts/// <reference path="Validation.ts" />namespace Validation {const numberRegexp = /^[0-9]+$/;export class ZipCodeValidator implements StringValidator {isAcceptable(s: string) {return s.length === 5 && numberRegexp.test(s);}}}
Test.ts
ts/// <reference path="Validation.ts" />/// <reference path="LettersOnlyValidator.ts" />/// <reference path="ZipCodeValidator.ts" />// Some samples to trylet strings = ["Hello", "98052", "101"];// Validators to uselet validators: { [s: string]: Validation.StringValidator } = {};validators["ZIP code"] = new Validation.ZipCodeValidator();validators["Letters only"] = new Validation.LettersOnlyValidator();// Show whether each string passed each validatorfor (let s of strings) {for (let name in validators) {console.log(`"${s}" - ${validators[name].isAcceptable(s) ? "matches" : "does not match"} ${name}`);}}
一旦涉及多個檔案,我們需要確保所有編譯後的程式碼都被載入。有兩種方法可以做到這一點。
首先,我們可以使用 outFile 選項進行級聯輸出,將所有輸入檔案編譯成一個單獨的 JavaScript 輸出檔案。
tsc --outFile sample.js Test.ts
編譯器將根據檔案中存在的引用標籤自動對輸出檔案進行排序。您也可以單獨指定每個檔案。
tsc --outFile sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts
或者,我們可以使用每個檔案編譯(預設設定),為每個輸入檔案生成一個 JavaScript 檔案。如果生成了多個 JS 檔案,我們需要在網頁中使用 <script> 標籤以正確的順序載入每個生成的指令碼檔案,例如:
MyTestPage.html (節選)
html<script src="Validation.js" type="text/javascript" /><script src="LettersOnlyValidator.js" type="text/javascript" /><script src="ZipCodeValidator.js" type="text/javascript" /><script src="Test.js" type="text/javascript" />
別名
簡化使用名稱空間的另一種方法是使用 import q = x.y.z 來為常用物件建立更短的名稱。不要將其與用於載入模組的 import x = require("name") 語法混淆,這種語法只是為指定的符號建立一個別名。您可以將此類匯入(通常稱為別名)用於任何型別的識別符號,包括從模組匯入中建立的物件。
tsnamespace Shapes {export namespace Polygons {export class Triangle {}export class Square {}}}import polygons = Shapes.Polygons;let sq = new polygons.Square(); // Same as 'new Shapes.Polygons.Square()'
注意,我們不使用 require 關鍵字;相反,我們直接從被匯入符號的限定名稱進行賦值。這類似於使用 var,但也適用於匯入符號的型別和名稱空間含義。重要的是,對於值而言,import 是對原始符號的獨特引用,因此對別名 var 的更改不會反映在原始變數中。
使用其他 JavaScript 庫
為了描述非 TypeScript 編寫的庫的形狀,我們需要宣告該庫暴露的 API。由於大多數 JavaScript 庫僅暴露少量頂級物件,因此名稱空間是表示它們的絕佳方式。
我們將不定義實現的宣告稱為“環境宣告”(ambient)。它們通常定義在 .d.ts 檔案中。如果您熟悉 C/C++,可以將它們視為 .h 檔案。讓我們看幾個例子。
環境名稱空間
流行的 D3 庫在一個名為 d3 的全域性物件中定義其功能。由於此庫是透過 <script> 標籤(而不是模組載入器)載入的,其宣告使用名稱空間來定義其形狀。為了讓 TypeScript 編譯器看到這種形狀,我們使用環境名稱空間宣告。例如,我們可以開始編寫如下程式碼:
D3.d.ts (簡化節選)
tsdeclare namespace D3 {export interface Selectors {select: {(selector: string): Selection;(element: EventTarget): Selection;};}export interface Event {x: number;y: number;}export interface Base extends Selectors {event: Event;}}declare var d3: D3.Base;