關於術語的說明:請注意,在 TypeScript 1.5 中,命名規則已變更。「內部模組」現在稱為「命名空間」。「外部模組」現在簡稱為「模組」,以符合 ECMAScript 2015 的術語(即
module X {
等同於現在較常用的namespace X {
)。
這篇文章概述了在 TypeScript 中使用命名空間(以前的「內部模組」)來組織程式碼的各種方式。正如我們在關於術語的說明中提到的,「內部模組」現在稱為「命名空間」。此外,在宣告內部模組時,任何使用 module
關鍵字的地方都可以且應該改用 namespace
關鍵字。這可避免讓新使用者因名稱相似的術語而感到困惑。
第一步
讓我們從我們將在整個頁面中用作範例的程式開始。我們撰寫了一小組簡化的字串驗證器,就像您可能撰寫的程式一樣,用於檢查網頁中表單上的使用者輸入或檢查外部提供的資料檔案格式。
單一檔案中的驗證器
ts
interface 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
。
命名空間驗證器
ts
namespace 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
ts
namespace 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")
語法不同,這個語法只是建立指定符號的別名。您可以將這些類型的匯入(通常稱為別名)用於任何類型的識別碼,包括從模組匯入中建立的物件。
ts
namespace 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 函式庫只公開少數頂層物件,因此命名空間是表示它們的絕佳方法。
我們稱呼未定義實作的宣告為「環境」。通常這些會定義在 .d.ts
檔案中。如果您熟悉 C/C++,可以將這些視為 .h
檔案。讓我們來看幾個範例。
環境命名空間
熱門函式庫 D3 在名為 d3
的全域物件中定義其功能。由於此函式庫是透過 <script>
標籤 (而非模組載入器) 載入,因此其宣告會使用命名空間來定義其形狀。為了讓 TypeScript 編譯器看到此形狀,我們使用環境命名空間宣告。例如,我們可以開始撰寫如下內容
D3.d.ts (簡化摘錄)
ts
declare 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;