這篇文章描述了如何在TypeScript里使用命名空間(之前叫做“內(nèi)部模塊”)來組織你的代碼。
就像我們在術(shù)語說明里提到的那樣,“內(nèi)部模塊”現(xiàn)在叫做“命名空間”。
另外,任何使用module關(guān)鍵字來聲明一個內(nèi)部模塊的地方都應(yīng)該使用namespace關(guān)鍵字來替換。
這就避免了讓新的使用者被相似的名稱所迷惑。
我們先來寫一段程序并將在整篇文章中都使用這個例子。 我們定義幾個簡單的字符串驗證器,假設(shè)你會使用它們來驗證表單里的用戶輸入或驗證外部數(shù)據(jù)。
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 try
let strings = ["Hello", "98052", "101"];
// Validators to use
let validators: { [s: string]: StringValidator; } = {};
validators["ZIP code"] = new ZipCodeValidator();
validators["Letters only"] = new LettersOnlyValidator();
// Show whether each string passed each validator
strings.forEach(s => {
for (let name in validators) {
console.log(""" + s + "" " + (validators[name].isAcceptable(s) ? " matches " : " does not match ") + name);
}
});
隨著更多驗證器的加入,我們需要一種手段來組織代碼,以便于在記錄它們類型的同時還不用擔(dān)心與其它對象產(chǎn)生命名沖突。 因此,我們把驗證器包裹到一個命名空間內(nèi),而不是把它們放在全局命名空間下。
下面的例子里,把所有與驗證器相關(guān)的類型都放到一個叫做Validation的命名空間里。
因為我們想讓這些接口和類在命名空間之外也是可訪問的,所以需要使用export。
相反的,變量lettersRegexp和numberRegexp是實現(xiàn)的細節(jié),不需要導(dǎo)出,因此它們在命名空間外是不能訪問的。
在文件末尾的測試代碼里,由于是在命名空間之外訪問,因此需要限定類型的名稱,比如Validation.LettersOnlyValidator。
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 try
let strings = ["Hello", "98052", "101"];
// Validators to use
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// Show whether each string passed each validator
strings.forEach(s => {
for (let name in validators) {
console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`);
}
});
當應(yīng)用變得越來越大時,我們需要將代碼分離到不同的文件中以便于維護。
現(xiàn)在,我們把Validation命名空間分割成多個文件。
盡管是不同的文件,它們?nèi)允峭粋€命名空間,并且在使用的時候就如同它們在一個文件中定義的一樣。
因為不同文件之間存在依賴關(guān)系,所以我們加入了引用標簽來告訴編譯器文件之間的關(guān)聯(lián)。
我們的測試代碼保持不變。
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
}
/// <reference path="Validation.ts" />
namespace Validation {
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
}
/// <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);
}
}
}
/// <reference path="Validation.ts" />
/// <reference path="LettersOnlyValidator.ts" />
/// <reference path="ZipCodeValidator.ts" />
// Some samples to try
let strings = ["Hello", "98052", "101"];
// Validators to use
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// Show whether each string passed each validator
strings.forEach(s => {
for (let name in validators) {
console.log(""" + s + "" " + (validators[name].isAcceptable(s) ? " matches " : " does not match ") + name);
}
});
當涉及到多文件時,我們必須確保所有編譯后的代碼都被加載了。 我們有兩種方式。
第一種方式,把所有的輸入文件編譯為一個輸出文件,需要使用--outFile標記:
tsc --outFile sample.js Test.ts
編譯器會根據(jù)源碼里的引用標簽自動地對輸出進行排序。你也可以單獨地指定每個文件。
tsc --outFile sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts
第二種方式,我們可以編譯每一個文件(默認方式),那么每個源文件都會對應(yīng)生成一個JavaScript文件。
然后,在頁面上通過<script>標簽把所有生成的JavaScript文件按正確的順序引進來,比如:
<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')語法弄混了,這里的語法是為指定的符號創(chuàng)建一個別名。
你可以用這種方法為任意標識符創(chuàng)建別名,也包括導(dǎo)入的模塊中的對象。
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關(guān)鍵字,而是直接使用導(dǎo)入符號的限定名賦值。
這與使用var相似,但它還適用于類型和導(dǎo)入的具有命名空間含義的符號。
重要的是,對于值來講,import會生成與原始符號不同的引用,所以改變別名的var值并不會影響原始變量的值。
為了描述不是用TypeScript編寫的類庫的類型,我們需要聲明類庫導(dǎo)出的API。 由于大部分程序庫只提供少數(shù)的頂級對象,命名空間是用來表示它們的一個好辦法。
我們稱其為聲明是因為它不是外部程序的具體實現(xiàn)。
我們通常在.d.ts里寫這些聲明。
如果你熟悉C/C++,你可以把它們當做.h文件。
讓我們看一些例子。
流行的程序庫D3在全局對象d3里定義它的功能。
因為這個庫通過一個<script>標簽加載(不是通過模塊加載器),它的聲明文件使用內(nèi)部模塊來定義它的類型。
為了讓TypeScript編譯器識別它的類型,我們使用外部命名空間聲明。
比如,我們可以像下面這樣寫:
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 let d3: D3.Base;