TypeScript——模块(2)

简单示例

下面我们来整理一下前面的验证器实现,每个模块只有一个命名的导出。

为了编译,我们必需要在命令行上指定一个模块目标。对于Node.js来说,使用--module commonjs; 对于Require.js来说,使用--module amd。比如:

tsc --module commonjs Test.ts

编译完成后,每个模块会生成一个单独的.js文件。 好比使用了reference标签,编译器会根据 import语句编译相应的文件。

Validation.ts

export interface StringValidator {

    isAcceptable(s: string): boolean;

}

LettersOnlyValidator.ts

import { StringValidator } from "./Validation";

const lettersRegexp = /^[A-Za-z]+$/;

export class LettersOnlyValidator implements StringValidator {

    isAcceptable(s: string) {

        return lettersRegexp.test(s);

    }

}

ZipCodeValidator.ts

import { StringValidator } from "./Validation";

const numberRegexp = /^[0-9]+$/;

export class ZipCodeValidator implements StringValidator {

    isAcceptable(s: string) {

        return s.length === 5 && numberRegexp.test(s);

    }

}

Test.ts

import { StringValidator } from "./Validation";

import { ZipCodeValidator } from "./ZipCodeValidator";

import { LettersOnlyValidator } from "./LettersOnlyValidator";

// 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 }`);

    }

});

可选的模块加载和其它高级加载场景

有时候,你只想在某种条件下才加载某个模块。 在TypeScript里,使用下面的方式来实现它和其它的高级加载场景,我们可以直接调用模块加载器并且可以保证类型完全。

编译器会检测是否每个模块都会在生成的JavaScript中用到。 如果一个模块标识符只在类型注解部分使用,并且完全没有在表达式中使用时,就不会生成 require这个模块的代码。 省略掉没有用到的引用对性能提升是很有益的,并同时提供了选择性加载模块的能力。

这种模式的核心是import id = require("...")语句可以让我们访问模块导出的类型。 模块加载器会被动态调用(通过 require),就像下面if代码块里那样。 它利用了省略引用的优化,所以模块只在被需要时加载。 为了让这个模块工作,一定要注意 import定义的标识符只能在表示类型处使用(不能在会转换成JavaScript的地方)。

为了确保类型安全性,我们可以使用typeof关键字。 typeof关键字,当在表示类型的地方使用时,会得出一个类型值,这里就表示模块的类型。

示例:Node.js里的动态模块加载

declare function require(moduleName: string): any;

import { ZipCodeValidator as Zip } from "./ZipCodeValidator";

if (needZipValidation) {

    let ZipCodeValidator: typeof Zip = require("./ZipCodeValidator");

    let validator = new ZipCodeValidator();

    if (validator.isAcceptable("...")) { /* ... */ }

}

示例:require.js里的动态模块加载

declare function require(moduleNames: string[], onLoad: (...args: any[]) => void): void;

import * as Zip from "./ZipCodeValidator";

if (needZipValidation) {

    require(["./ZipCodeValidator"], (ZipCodeValidator: typeof Zip) => {

        let validator = new ZipCodeValidator.ZipCodeValidator();

        if (validator.isAcceptable("...")) { /* ... */ }

    });

}

示例:System.js里的动态模块加载

declare const System: any;

import { ZipCodeValidator as Zip } from "./ZipCodeValidator";

if (needZipValidation) {

    System.import("./ZipCodeValidator").then((ZipCodeValidator: typeof Zip) => {

        var x = new ZipCodeValidator();

        if (x.isAcceptable("...")) { /* ... */ }

    });

}

使用其它的JavaScript库

要想描述非TypeScript编写的类库的类型,我们需要声明类库所暴露出的API。

我们叫它声明因为它不是“外部程序”的具体实现。 它们通常是在 .d.ts文件里定义的。 如果你熟悉C/C++,你可以把它们当做 .h文件。 让我们看一些例子。

外部模块

在Node.js里大部分工作是通过加载一个或多个模块实现的。 我们可以使用顶级的 export声明来为每个模块都定义一个.d.ts文件,但最好还是写在一个大的.d.ts文件里。 我们使用与构造一个外部命名空间相似的方法,但是这里使用 module关键字并且把名字用引号括起来,方便之后import。 例如:

node.d.ts (simplified excerpt)

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 let sep: string;

}

现在我们可以/// node.d.ts并且使用import url = require("url");或import * as URL from "url"加载模块。

///

import * as URL from "url";

let myUrl = URL.parse("http://www.typescriptlang.org");

外部模块简写

假如你不想在使用一个新模块之前花时间去编写声明,你可以采用声明的简写形式以便能够快速使用它。

declarations.d.ts

declare module "hot-new-module";

简写模块里所有导出的类型将是any。

import x, {y} from "hot-new-module";

x(y);

模块声明通配符

某些模块加载器如SystemJS 和 AMD支持导入非JavaScript内容。 它们通常会使用一个前缀或后缀来表示特殊的加载语法。 模块声明通配符可以用来表示这些情况。

declare module "*!text" {

    const content: string;

    export default content;

}

// Some do it the other way around.

declare module "json!*" {

    const value: any;

    export default value;

}

现在你可以就导入匹配"*!text"或"json!*"的内容了。

import fileContent from "./xyz.txt!text";

import data from "json!http://example.com/data.json";

console.log(data, fileContent);

UMD模块

有些模块被设计成兼容多个模块加载器,或者不使用模块加载器(全局变量)。 它们以 UMD模块为代表。 这些库可以通过导入的形式或全局变量的形式访问。 例如:

math-lib.d.ts

export function isPrime(x: number): boolean;

export as namespace mathLib;

之后,这个库可以在某个模块里通过导入来使用:

import { isPrime } from "math-lib";

isPrime(2);

mathLib.isPrime(2); // 错误: 不能在模块内使用全局定义。

它同样可以通过全局变量的形式使用,但只能在某个脚本(指不带有模块导入或导出的脚本文件)里。

mathLib.isPrime(2);

你可能感兴趣的:(TypeScript——模块(2))