TypeScript Office - 命名空间
# 命名空间
关于术语的说明:需要注意的是,在 TypeScript 1.5 中,术语已经改变。「内部模块」现在是「命名空间」。「外部模块」现在只是「模块」,以便与 ECMAScript 2015 的术语保持一致,(即 module X {
等同于现在的 namespace X {
)。
这篇文章概述了在 TypeScript 中使用命名空间(以前的「内部模块」),用各种方法来组织你的代码。正如我们在术语说明中所暗示的,「内部模块」现在被称为「命名空间」。此外,在声明内部模块时,凡是使用 module 关键字的地方,都可以而且应该使用 namespace 关键字来代替。这就避免了新用户因使用类似的术语而感到困惑。
# 第一步
让我们从本页中我们将使用的程序开始。作为例子,我们写了一小套简单的字符串验证器,用来检查用户在网页中的表单中的输入,或者检查外部提供的数据文件的格式。
# 单一文件中的验证器
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);
}
}
// 一些测试案例
let strings = ["Hello", "98052", "101"];
// 要使用的验证器
let validators: { [s: string]: StringValidator } = {};
validators["ZIP code"] = new ZipCodeValidator();
validators["Letters only"] = new LettersOnlyValidator();
// 显示每一个字符串是否通过了每个验证器
for (let s of strings) {
for (let name in validators) {
let isMatch = validators[name].isAcceptable(s);
console.log(`'${s}' ${isMatch ? "matches" : "does not match"} '${name}'.`);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 命名方式
当我们添加更多的验证器时,我们会希望有某种组织方案,这样我们就可以跟踪我们的类型,而不用担心与其他对象的名称冲突。与其把很多不同的名字放到全局命名空间中,不如把我们的对象包装成一个命名空间。
在这个例子中,我们将把所有与验证器相关的实体移到一个叫做 Validation 的命名空间中。因为我们希望这里的接口和类在命名空间之外是可见的,所以我们在它们前面加上 export。相反,变量 lettersRegexp 和 numberRegexp 是实现细节,所以它们没有被导出,也不会被命名空间以外的代码看到。在文件底部的测试代码中,我们现在需要限定在名字空间之外使用的类型的名称,例如 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);
}
}
}
// 一些测试案例
let strings = ["Hello", "98052", "101"];
// 要使用的验证器
let validators: { [s: string]: Validation.StringValidator } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// 显示每一个字符串是否通过了每个验证器
for (let s of strings) {
for (let name in validators) {
console.log(
`"${s}" - ${
validators[name].isAcceptable(s) ? "matches" : "does not match"
} ${name}`
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 跨文件分割
随着我们的应用程序的增长,我们将希望把代码分成多个文件,以使它更容易维护。
# 多文件命名空间
在这里,我们将把我们的 Validation 命名空间分成许多文件。尽管这些文件是分开的,但它们都可以为同一个命名空间做出贡献,并且可以像在一个地方定义一样被使用。由于文件之间存在依赖关系,我们将添加引用标签来告诉编译器这些文件之间的关系。我们的测试代码在其他方面没有变化。
Validation.ts
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
}
2
3
4
5
LettersOnlyValidator.ts
/// <reference path="Validation.ts" />
namespace Validation {
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
}
2
3
4
5
6
7
8
9
ZipCodeValidator.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);
}
}
}
2
3
4
5
6
7
8
9
Test.ts
/// <reference path="Validation.ts" />
/// <reference path="LettersOnlyValidator.ts" />
/// <reference path="ZipCodeValidator.ts" />
// 一些测试案例
let strings = ["Hello", "98052", "101"];
// 要使用的验证器
let validators: { [s: string]: Validation.StringValidator } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// 显示每一个字符串是否通过了每个验证器
for (let s of strings) {
for (let name in validators) {
console.log(
`"${s}" - ${
validators[name].isAcceptable(s) ? "matches" : "does not match"
} ${name}`
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
一旦涉及到多个文件,我们就需要确保所有的编译后的代码都能被加载。有两种方法可以做到这一点。
首先,我们可以使用 outFile 选项进行串联输出,将所有的输入文件编译成一个单一的 JavaScript 输出文件。
tsc --outFile sample.js Test.ts
编译器将根据文件中存在的参考标签自动排列输出文件。你也可以单独指定每个文件:
tsc --outFile sample.js Validation.ts LettersOnlyValidator.ts
ZipCodeValidator.ts Test.ts
2
另外,我们也可以使用按文件编译(默认),为每个输入文件生成一个 JavaScript 文件。如果产生了多个 JS 文件,我们就需要在网页上使用 <script>
标签,以适当的顺序加载每个发射的文件,例如:
MyTestPage.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" />
2
3
4
# 别名
另一个可以简化命名空间工作的方法是使用 import q = x.y.z
来为常用对象创建更短的名称。不要与用于加载模块的 import x = require("name")
语法相混淆,这种语法只是为指定的符号创建一个别名。你可以为任何类型的标识符使用这类导入(通常被称为别名),包括从模块导入创建的对象。
namespace Shapes {
export namespace Polygons {
export class Triangle {}
export class Square {}
}
}
import polygons = Shapes.Polygons;
let sq = new polygons.Square(); // 与'new Shapes.Polygons.Square()'等价
2
3
4
5
6
7
8
注意,我们没有使用 require 关键字;相反,我们直接从我们要导入的符号的限定名称中分配。这类似于使用 var,但也适用于导入符号的类型和命名空间的含义。重要的是,对于数值来说,导入是一个不同于原始符号的引用,所以对别名 var 的改变不会反映在原始变量上。
# 与其他JavaScript库一起工作
为了描述不是用 TypeScript 编写的库的形状,我们需要声明库所暴露的 API。因为大多数 JavaScript 库只暴露了几个顶级对象,命名空间是表示它们的一个好方法。
我们把不定义实现的声明称为「环境」。通常,这些都是在 .d.ts
文件中定义的。如果你熟悉 C/C++,你可以把它们看作是 .h
文件。让我们来看看几个例子:
D3.d.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;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16