Typescript中文教程-模块

1、第一步

让我们用我们通篇都用的这个例子来开始吧。我们将写一个最简单的验证器。就像当你检查一个用户给页面提交一个表单或检查外部提供的数据文件的格式时可能用到的那样。

1
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
interface StringValidator {
    isAcceptable(s: string): boolean;
}
var lettersRegexp = /^[A-Za-z]+$/;
var 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
var strings = [‘Hello’, ‘98052’, ‘101’];
// Validators to use
var 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 (var name in validators) {
        console.log(‘”‘ + s + ‘” ‘ + (validators[name].isAcceptable(s) ? ‘ matches ‘ : ‘ does not match ‘) + name);
    }
});

 

2、添加模块

当我们添加更多的验证器时,我们想要一种能保持跟踪我们的类型并且不用担心和其他对象名发生冲突的组织结构。咱们把对象放进模块里而不用把很多不同的名字放进全局的命名空间里。

在这个例子里,我们把所有验证器相关的类型放进一个叫Validation的模块里。因为我们希望在模块外可见其类和接口,我们用关键字export引用之。相反的,变量lettersRegexp和numberRegexp是实现的细节,所以不会被引用并且在外部是不可见的。

Modularized Validators

1
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
module Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }
    var lettersRegexp = /^[A-Za-z]+$/;
    var 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
var strings = [‘Hello’, ‘98052’, ‘101’];
// Validators to use
var 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 (var name in validators) {
        console.log(‘”‘ + s + ‘” ‘ + (validators[name].isAcceptable(s) ? ‘ matches ‘ : ‘ does not match ‘) + name);
    }
});

 

3、分成多个文件

随着我们应用的增长,我们想将代码分成多个文件以易于维护。

这里我们将 Validation模块分成多个文件。甚至即使几个文件分割开来,在同一个地方定义的对同一个模块仍然互相有作用。因为有文件之间的依赖关系,我们添加了参考标签告诉编译器文件之间的关系。我们的测试代码仍然不变不变。

Validation.ts

1
2
3
4
5
module Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }
}

LettersOnlyValidator.ts

1
2
3
4
5
6
7
8
9
/// <reference path=”Validation.ts” />
module Validation {
    var lettersRegexp = /^[A-Za-z]+$/;
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }
}

ZipCodeValidator.ts

1
2
3
4
5
6
7
8
9
/// <reference path=”Validation.ts” />
module Validation {
    var numberRegexp = /^[0-9]+$/;
    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
}

Test.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// <reference path=”Validation.ts” />
/// <reference path=”LettersOnlyValidator.ts” />
/// <reference path=”ZipCodeValidator.ts” />
// Some samples to try
var strings = [‘Hello’, ‘98052’, ‘101’];
// Validators to use
var 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 (var name in validators) {
        console.log(‘”‘ + s + ‘” ‘ + (validators[name].isAcceptable(s) ? ‘ matches ‘ : ‘ does not match ‘) + name);
    }
});

 

4、编译

一旦涉及多个文件,我们需要确保所有的编译后的代码被加载。有两种方式实现。

首先,我们使用 –out 标签将所有输入文件串联起来成一个单独的 JavaScript 输出文件:

1
tsc –out sample.js Test.ts

编译器将按照文件内的参考标签自动排布文件顺序。你也可以单独制定各个文件:

1
tsc –out sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts

我们可以使用文件编译(默认)为每个输入文件输出一个JavaScript文件。如果多个JS文件,我们需要在网页上使用<script>标签以合适的顺序加载输出文件。例如:

MyTestPage.html (excerpt)

1
2
3
4
  <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” />

 

5、使用外部模块

TypeScript 也有外部模块的概念。使用外部模块的两种情况:node.js 和require.js. 不使用 node.js和require.js 的应用不必使用外部模块也可以很好的用上面提到的内部模块来组织起来。

在外部模块,文件间的关系依照 imports和exports 在文件级别上指定。在TypeScript任何文件包含一个顶级import或export被认为是外部模块

下面,我们将之前的例子转变成使用外部模块的。注意我们不再使用module关键词,文件本身就是由其文件名构成的模块。

参考标签被换成 import声明来确认模块间的依存关系. Import声明有两部分:由文件名所确认的名字和require关键字所确认的模块路径。

1
import someMod = require(‘someModule’);

我们通过使用导出关键字在一个顶级声明指定哪些对象是可见的外部模块,类似于在内部模块里定义公共区域。

编译时,我们必须使用一个特殊的模块标记在命令行里。例如,node.js使用 –module commonjs;对于require.js使用 — uodele amd 。例如:

1
tsc –module commonjs Test.ts

当编译时,每个外部模块都将是一个独立的.js文件。跟参考标签一样,编译器将会根据输出声明来编译文件依存关系。

Validation.ts

1
2
3
export interface StringValidator {
    isAcceptable(s: string): boolean;
}

LettersOnlyValidator.ts

1
2
3
4
5
6
7
import validation = require(‘./Validation’);
var lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements validation.StringValidator {
    isAcceptable(s: string) {
        return lettersRegexp.test(s);
    }
}

ZipCodeValidator.ts

1
2
3
4
5
6
7
import validation = require(‘./Validation’);
var numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator implements validation.StringValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}

Test.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import validation = require(‘./Validation’);
import zip = require(‘./ZipCodeValidator’);
import letters = require(‘./LettersOnlyValidator’);
// Some samples to try
var strings = [‘Hello’, ‘98052’, ‘101’];
// Validators to use
var validators: { [s: string]: validation.StringValidator; } = {};
validators[‘ZIP code’] = new zip.ZipCodeValidator();
validators[‘Letters only’] = new letters.LettersOnlyValidator();
// Show whether each string passed each validator
strings.forEach(s => {
    for (var name in validators) {
        console.log(‘”‘ + s + ‘” ‘ + (validators[name].isAcceptable(s) ? ‘ matches ‘ : ‘ does not match ‘) + name);
    }
});

 

6、代码生成外部模块

根据编译时指定的目标模块,编译器会生为 node.js [commonjs]或 require.js [amd] 模块载入系统。更多关于生成代码时调用哪个定义和需求,查阅相应的模块载入器。

这个例子展示了输入和输出模块代码时时名字是如何被翻译的。

SimpleModule.ts

1
2
import m = require(‘mod’);
export var t = m.something + 1;

AMD / RequireJS SimpleModule.js:

1
2
3
define([“require”, “exports”, ‘mod’], function(require, exports, m) {
    exports.t = m.something + 1;
});

CommonJS / Node SimpleModule.js:

1
2
var m = require(‘mod’);
exports.t = m.something + 1;

 

7、Export =

在先前的例子里,当我们使用每个验证器时每个模块只输出一个值。在这样的情况下,当只使用一个符号也能很好的工作时使用这些他们限定的符号很麻烦。

export = syntax 指定一个模块里导出的单一对象。可以是类,接口,函数或枚举类型。当导入时,这个输出符号将被直接使用并且不会被任何名字限制。

下面,我们使用一个从每个模块里用export= syntax导出的对象简化Validator。这个简化代码简单的使用‘zipValidator’而不用’zip.ZipCodeValidator’。

Validation.ts

1
2
3
export interface StringValidator {
    isAcceptable(s: string): boolean;
}

LettersOnlyValidator.ts

1
2
3
4
5
6
7
8
import validation = require(‘./Validation’);
var lettersRegexp = /^[A-Za-z]+$/;
class LettersOnlyValidator implements validation.StringValidator {
    isAcceptable(s: string) {
        return lettersRegexp.test(s);
    }
}
export = LettersOnlyValidator;

ZipCodeValidator.ts

1
2
3
4
5
6
7
8
import validation = require(‘./Validation’);
var numberRegexp = /^[0-9]+$/;
class ZipCodeValidator implements validation.StringValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}
export = ZipCodeValidator;

Test.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import validation = require(‘./Validation’);
import zipValidator = require(‘./ZipCodeValidator’);
import lettersValidator = require(‘./LettersOnlyValidator’);
// Some samples to try
var strings = [‘Hello’, ‘98052’, ‘101’];
// Validators to use
var validators: { [s: string]: validation.StringValidator; } = {};
validators[‘ZIP code’] = new zipValidator();
validators[‘Letters only’] = new lettersValidator();
// Show whether each string passed each validator
strings.forEach(s => {
    for (var name in validators) {
        console.log(‘”‘ + s + ‘” ‘ + (validators[name].isAcceptable(s) ? ‘ matches ‘ : ‘ does not match ‘) + name);
    }
});

 

8、别名

另一种可以简化这种模块是对常用对象使用 import q = x.y.z 这种短名字。不必为import x = require(‘name’) 这种载入模块的符号迷糊,这个语法创建了一个指定符号的别名。你可以使用任何一种符号给这种输入{普通参考至别名},包括输入的输出模块创建的对象。

Basic Aliasing

1
2
3
4
5
6
7
8
9
module Shapes {
    export module Polygons {
        export class Triangle { }
        export class Square { }
    }
}
import polygons = Shapes.Polygons;
var sq = new polygons.Square(); // Same as ‘new Shapes.Polygons.Square()’

 

9、使用其他JavaScript库

为了描述TypeScript没有的库的类型,我们需要声明这个库暴露的API。因为大多数JavaScript库只暴露一些顶层对象,模块是很好的表现他们的方法。我们称之为声明不能定义一个实现“环境”。通常这些定义在.d.ts文件中,如果你熟悉c或c++可以把这些想象成 .h或 ‘extern’。让我们看一下一些既有内部又有外部的例子。

内部模块环境

著名的库D3定义其功能在一个全局的对象叫做‘D3’里。因为这个库是通过一个 Script标签而不是一个模块载入的,其声明通过内部模块定义其形状。为了使TypeScript查看其形状,我们使用一个内部环境模块声明。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
declare module 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;

D3.d.ts (simplified excerpt)

外部环境模块

在 node.js,大多数任务通过载入一个或多个模块来完成。我们可以定义其每个自己的 .d.ts模块使用高级输出声明。但更方便一点的是将其写在一个长一点的.d.ts 文件里。为了这么做,我们引用模块的名字。其有可能成为一个长的输入。例子如下:

node.d.ts (simplified excerpt)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
declare module “url” {
    export interface Url {
        protocol?: string;
        hostname?: string;
        pathname?: string;
    }
    export function parse(urlStr: string, parseQueryString?, slashesDenoteHost?): Url;
}
declare module “path” {
    export function normalize(p: string): string;
    export function join(…paths: any[]): string;
    export var sep: string;
}

现在我们可以使用 /// <reference> node.d.ts 标签然后载入这个模块使用 例如

1
2
3
///<reference path=”node.d.ts”/>
import url = require(“url”);
var myUrl = url.parse(“http://www.typescriptlang.org”);

 

10、模块陷阱

这部分我们将讨论几种常见的使用内部和外部的模块陷阱和如何避免他们。

/// <reference> to an external module

一个常见的错误是使用///<reference> 标签来指定外部模块文件而不是使用import 。为了弄清楚其中的差别,我们先要了解三种编译器可以定位外部模块文件信息的方法。

提议中是找到一个用 import x = require(..) 声明命名的 .ts文件 。这个文件必须是一个有着顶级 import或export声明的文件。

第二种是找到一个.d.ts文件。跟上面一样,除了一个实际的文件,也要有顶级 import或export声明。

最后一种是使用“外部文件环境声明”。我们用一个合适的名字‘declare’之。

myModules.d.ts

1
2
3
4
// In a .d.ts file or .ts file that is not an external module:
declare module “SomeModule” {
    export function fn(): string;
}

myOtherModule.ts

1
2
/// <reference path=”myModules.d.ts” />
import m = require(“SomeModule”);

这个 reference标签让我们找到包含外部环境模块声明的文件。这是几个TypeScript例子里使用node.d.ts 文件的方法。

不必要的命名空间

如果你正把一个内部模块转换成外部模块,像下面这样来结束这个文件很容易:
shapes.ts

1
2
3
4
export module Shapes {
    export class Triangle { /* … */ }
    export class Square { /* … */ }
}

顶级模块形状shape包括三角Triangle 和四边形Square没什么用处。这将让使用你的模块的用户产生困惑和厌烦。

shapeConsumer.ts

1
2
import shapes = require(‘./shapes’);
var t = new shapes.Shapes.Triangle(); // shapes.Shapes?

TypeScript里外部模块的一个关键特性是两个外部模块从不贡献相同的命名范围。因为模块的用户决定如何设置他,没有必要在命名空间里主动的包含输出符号。

重申一下为什么不应该尝试给你的外部模块内容使用命名空间,命名空间的核心思想是保护本地组结构和防止命名冲突。因为外部模块本身就是一个本地组,他的顶级名称被其输入的代码所定义,所以没必要加一个额外的模块层给输出对象。

修订的例子:
shapes.ts

1
2
export class Triangle { /* … */ }
export class Square { /* … */ }

shapeConsumer.ts

1
2
import shapes = require(‘./shapes’);
var t = new shapes.Triangle();

 

权衡外部模块

就像JS文件和其模块有一对一的通信。TypeScript在额外模块源文件和他放出的JS文件也有一个一对一通信。其影响是不能使用 –out 编译器切换至链接多个外部模块源文件进一个单独的JavaScript文件。

发表评论