愿你坚持不懈,努力进步,进阶成自己理想的人

—— 2017.09, 写给3年后的自己

Typescript学习记录:模块

一、介绍

从TypeScript 1.5起,术语名称有了一些变化:内部模块现称为命名空间外部模块现称为模块


二、支持ES6的模块

TypeScript支持ES6模块,其用法与ES6 Module基本一致(不同的是也支持导出TS的特定语法),如:

1、导出

1)可以导出任何声明(变量、函数、类、类型别名、接口),如:

export interface ISomeInterface {
    // ...
}
export const someConstVar = 123
export class SomeClass implements ISomeInterface {
    // ...
}

2)可以重命名导出,如:

export class SomeClass implements ISomeInterface {
    // ...
}
export { SomeClass as SC }

3)也可以重新导出,如:

export * from 'someModule'
export { someMethod as someMethodAnotherName } from 'someModule'

4)默认导出
每个模块都可以有且只能有一个default导出,如:

// jQuery.d.ts
declare let $: JQuery
export default $

// App.ts
import $ from 'jQuery'

默认导出可以使得在import时无需加花括号,而且导入时可以任意命名,默认导出可以是任何表达式和声明,包括一个值都行,如:

export default '123'

2、导入

1)可以导入一个模块中的特定内容,如:

import { someMethod } from 'someModule'

2)可以对导入的内容重命名,如:

import { someMethod as anotherMethodName } from 'someModule'

3)可以将整个模块导入,并通过一个变量访问,如:

import * as someVar from 'someModule'
someVar.someProp
someVar.someMethod()

4)可以导入具有副作用的模块
有些模块不需要导出任何值,但却有副作用(比如模块内设置一些全局状态供其他模块使用),这类模块通常形式如下:

import './someModule.js'


三、兼容CommonJS/AMD

CommonJS和AMD中,都有一个exports对象的概念,其包含了一个模块的所有导出内容。当我们将exports替换为一个自定义对象时,就相当于ES Module里的默认导出
TypeScript也能够兼容CommonJS或者AMD,主要通过以下两个语法实现,即:

  • export = 定义导出的对象,可以是类、接口、命名空间、函数或者枚举
  • import moduleName = require('module') 用于导入一个使用了export =的模块

示例如:

// lib.ts文件
function isValidEmail(email: string) {
    // ...
}
export = isValidEmail

// App.js文件
import isValidEmail = require('./lib.ts')
let email = 'example@163.com'
console.log(email, 'is', isValidEmail(email) ? 'valid' : 'invalid')

TypeScript会根据我们制定的模块目标参数,生成相应的供Node.js(CommonJS)、Require.js(AMD)、isomorphic(UMD)等模块加载系统使用的代码,例子如:
示例代码:

import m = require('mod')
export let t = m.something + 1

1)AMD

define(['require', 'exports', './mod'], function(require, exports, mod_1) {
    exports.t = mod_1.something + 1
})

2)CommonJS

let mod_1 = require('./mod')
exports.t = mod_1.something + 1

3)UMD

(function(factory) {
    if (typeof module === 'object' && typeof module.exports === 'object') {
        let v = factory(require, exports)
        if (v !== undefined) {
            module.exports = v
        } else if (typeof define === 'function' && !define.amd) {
            define(['require', 'exports', './mod'], factory)
        }
    }
})(function(require, exports) {
    let mod_1 = require('./mod')
    exports.t = mod_1.something + 1
})

指定编译输出采用的模块系统的方式为:

$ tsc --module type FileName.ts

如:$ tsc --module commonjs App.ts


四、使用外部JavaScript库

当使用外部JavaScript库时,我们可能会遇到一个问题:由于外部JavaScript库不是使用TypeScript所编写,所以也就不具有类型,那么我们又该如何在TypeScript里结合两者呢?
答案是,可以使用declare关键字,并在.d.ts文件里写定义(它相当于C/C++里的.h头文件),如:
node.d.ts

declare module 'url' {
    export interface Url {
        protocol?: string
        hostname?: string
        pathname?: string
    }
    export function parse(urlStr: string, parseQueryString?, slashesDenoteHost?): Url
}
// ...

现在,我们就可以在我们的TS项目中使用了:
先使用/// <reference path='node.d.ts' />这种形式引入定义文件,然后:

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

外部模块的简写

如果我们不想在使用一个模块之前还要去写声明,可以使用声明的简写形式来快速使用,如此一来,所有导出的类型都是any,如:
declaration.d.ts

declare module 'hot-new-module'

那么,以下模块里所有导出的类型都是any:

import x, { y } from 'hot-new-module'
x(y)

模块声明通配符

有些模块加载器允许导入非JavaScript的内容,通常会加一个前缀或者后缀来表示特殊的加载语法,如:

import template from 'text!./tpl.html'

我们可以使用通配符来让TypeScript支持,如:

declare module '*!text' {
    // ...
}
declare module 'json!*' {
    // ...
}