Namespaces
命名空间
关于术语的说明:
重要的是要注意,在 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
}
}
// 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
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 }'.`
}
}
命名空间
当我们添加更多验证器时,我们希望拥有某种组织方案,以便跟踪我们的类型,而不必担心与其他对象的名称冲突。我们不必将大量不同的名称放入全局名称空间,而是将我们的对象包装到名称空间中。
在这个例子中,我们将所有与验证器相关的实体移动到名为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
}
}
}
// 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
for (let s of strings) {
for (let name in validators) {
console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`
}
}
跨文件分割
随着我们的应用程序的增长,我们希望将代码分割成多个文件,以便于维护。
多文件命名空间
在这里,我们将把我们的Validation
命名空间分割成多个文件。即使这些文件是分开的,它们每个都可以贡献给相同的名称空间,并且可以被使用,就好像它们都是在一个地方定义的一样。由于文件之间存在依赖关系,因此我们将添加引用标记以告知编译器关于文件之间的关系。我们的测试代码不变。
Validation.ts
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
}
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
}
}
}
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
}
}
}
Test.ts
/// <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
for (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 (excerpt)
<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" />
Aliases
您可以简化名称空间的另一种方法是使用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( // 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 (simplified excerpt)
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;