技术分享

使用TypeScript开发微信小程序(8)——模块(Module)


从ECMAScript 2015 开始,JavaScript 引入了模块的概念。TypeScript 也沿用这个概念。

模块功能主要由两个命令构成:export和import。export命令用于用户自定义模块,规定对外接口;import命令用于输入其他模块提供的功能。

模块

模块在其自身的作用域里执行,而不是在全局作用域里;这意味着定义在一个模块里的变量,函数,类等等在模块外部是不可见的,除非明确地使用 export形式之一导出它们。 相反,如果想使用其它模块导出的变量,函数,类,接口等的时候,必须要导入它们,可以使用 import 形式之一。

模块是自声明的;两个模块之间的关系是通过在文件级别上使用 imports 和 exports 建立的。

模块使用模块加载器去导入其它的模块。 在运行时,模块加载器的作用是在执行此模块代码前去查找并执行这个模块的所有依赖。

TypeScript 与 ECMAScript 2015 一样,任何包含顶级 import 或者 export 的文件都被当成一个模块。

导出

任何声明(比如变量,函数,类,类型别名或接口)都能够通过添加export关键字来导出。

    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }
    export 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);
        }
    }
    export { ZipCodeValidator };
    export { ZipCodeValidator as mainValidator };

可以扩展其它模块,并且只导出那个模块的部分内容。重新导出功能并不会在当前模块导入那个模块或定义一个新的局部变量。

    export class ParseIntBasedZipCodeValidator {
        isAcceptable(s: string) {            return s.length === 5 && parseInt(s).toString() === s;
        }
    }

    export {ZipCodeValidator as RegExpBasedZipCodeValidator} from "./ZipCodeValidator";

一个模块可以包裹多个模块,并把他们导出的内容联合在一起通过语法:export * from “module”。

    export * from "./Validation"; 
    export * from "./LettersOnlyValidator"; 
    export * from "./ZipCodeValidator";

导入

模块的导入操作与导出一样简单。 可以使用以下 import形式之一来导入其它模块中的导出内容。

    import { ZipCodeValidator } from "./ZipCodeValidator";    let myValidator = new ZipCodeValidator();

可以对导入内容重命名

    import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";    let myValidator = new ZCV();

将整个模块导入到一个变量,并通过它来访问模块的导出部分

    import * as validator from "./ZipCodeValidator";    let myValidator = new validator.ZipCodeValidator();

一些模块会设置一些全局状态供其它模块使用,这些模块可能没有任何的导出或用户根本就不关注它的导出。 使用下面的方法来导入这类模块:

    import "./my-module.js";

默认导出

每个模块都可以有一个default导出。 默认导出使用 default关键字标记;并且一个模块只能够有一个default导出。 需要使用一种特殊的导入形式来导入 default导出。

类和函数声明可以直接被标记为默认导出。 标记为默认导出的类和函数的名字是可以省略的。

default导出也可以是一个值

    export default "123";
    import num from "./OneTwoThree";    console.log(num); // 输出:123

export = 、import id = require(“…”) 语法

CommonJS 和 AMD 都有一个 exports 对象的概念,它包含了一个模块的所有导出内容。

它们也支持把 exports 替换为一个自定义对象。 默认导出就好比这样一个功能;然而,它们却并不相互兼容。 TypeScript模块支持 export = 语法以支持传统的 CommonJS 和 AMD 的工作流模型。

export = 语法定义一个模块的导出对象。 它可以是类,接口,命名空间,函数或枚举。

若要导入一个使用了export = 的模块时,必须使用TypeScript提供的特定语法 import let = require(“…”)。

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

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

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

使用其它的JavaScript库

要想描述非 TypeScript 编写的类库的类型,我们需要声明类库所暴露出的 API。 它们通常是在 .d.ts文件里定义的。

可以使用顶级的 export 声明来为每个模块都定义一个 .d.ts 文件,但最好还是写在一个大的 .d.ts 文件里。 使用 module 关键字并且把名字用引号括起来,方便之后import。

node.d.ts

declare module "url" {
    export interface Url {
        protocol?: string;
        hostname?: string;
        pathname?: string;
    }

    export function parse(urlStr: string, parseQueryString?: string, slashesDenoteHost?: string): 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”);加载模块。

    /// <reference path="node.d.ts"/>    import * as URL from "url";    let myUrl = URL.parse("http://www.com");

Triple-Slash Directives(三斜线指令)告诉编译器在编译过程中要引入的额外的文件。/// <reference path="..." /> 指令是三斜线指令中最常见的一种。 它用于声明文件间的依赖。

参考资料

  • TypeScript官网
  • TypeScript中文网

其他

  • 完整代码:https://github.com/guyoung/GyWxappCases/tree/master/TypeScript
  • 微信小程序Canvas增强组件WeZRender:https://github.com/guyoung/WeZRender