前端开发 / TypeScript 模块

TypeScript 模块

在没有使用模块化编程的时代,会经常遇到全局变量污染、变量重名、多个文件之间存在依赖关系,需要保证一定加载顺序等问题。在模块化这种规范被提出后,得到社区和广大开发者的积极响应。

本节将介绍 TypeScript 的模块化方案,学习模块的导入导出机制,要注意 TypeScript 是怎么样兼容 CommonJS 和 AMD 规范的。

1. 慕课解释

模块在其自身的作用域里执行,而不是在全局作用域里。

export: 导出模块中的变量、函数、类、接口等;

import: 导入其他模块导出的变量、函数、类、接口等。

TypeScript 与 ECMAScript 2015 一样,任何包含顶级 import 或者 export 的文件都被当成一个模块。相反的,如果一个文件不带有顶级的 import 或者 export 声明,那么它的内容被视为全局可见的。

2. 全局模块

在一个 TypeScript 工程创建一个 test.ts 文件,写入代码:

const a = 1

然后,在相同的工程下创建另一个 test2.ts 文件,写入代码:

const a = 2

此时编译器会提示重复定义错误,虽然是在不同的文件下,但处于同一全局空间。

如果加上 export 导出语句:

export const a = 1

这样,两个 a 因处于不同的命名空间,就不会报错。

3. 导出语法

3.1 使用 export 导出声明

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

export.ts:

export const a: number = 1
export const add = (x: number, y:number) => x + y 

export interface User {
  nickname: string,
  department: string
}
export class Employee implements User {
  public nickname!: string
  public department!: string
}

export type used = true | false

解释: 每个声明都通过 export 关键字导出。

3.2 先声明,后导出

const a: number = 1
const add = (x: number, y:number) => x + y 

interface User {
  nickname: string,
  department: string
}
class Employee implements User {
  public nickname!: string
  public department!: string
}

type used = true | false

export { a, add, Employee }

解释: 先进行声明操作,最终统一使用 export 关键字导出。

3.3 导出时重命名

const a: number = 1
const add = (x: number, y:number) => x + y 

interface User {
  nickname: string,
  department: string
}
class Employee implements User {
  public nickname!: string
  public department!: string
}

type used = true | false

export { add }
export { a as level, used as status, Employee }

解释: 在导出时,可以用 as 关键字将声明重命名。

3.4 重新导出

重新导出功能并不会在当前模块导入那个模块或定义一个新的局部变量。

ZipCodeValidator.ts:

export interface StringValidator {
  isAcceptable(s: string): boolean
}

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

class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
  return s.length === 5 && numberRegexp.test(s)
}
}
export { ZipCodeValidator }
export { ZipCodeValidator as mainValidator }

ParseIntBasedZipCodeValidator.ts:

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

// 导出原先的验证器但做了重命名
export {ZipCodeValidator as RegExpBasedZipCodeValidator} from './ZipCodeValidator'

代码解释: 在 ParseIntBasedZipCodeValidator.ts 文件中,重新导出 ZipCodeValidator.ts 文件中的声明。

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

比如在 validator.ts 文件中,统一导出这两个模块。

// validator.ts
export * from './ZipCodeValidator'
export * from './ParseIntBasedZipCodeValidator'

3.5 默认导出

export default class ZipCodeValidator {
  static numberRegexp = /^[0-9]+$/
  isAcceptable(s: string) {
    return s.length === 5 && ZipCodeValidator.numberRegexp.test(s)
  }
}

代码解释: 每个模块都可以有一个 default 导出,且一个模块只能够有一个 default 导出。

4. 导入语法

4.1 使用 import 导入

使用 import 形式来导入其它模块中的导出内容。

import { a, add, Employee } from './export'

4.2 导入时重命名

import { a as level, used as status } from './export'

4.3 将整个模块导入到一个变量

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

import * as TYPES from './export'

4.4 直接导入

import './export'

5. export =import = require()

CommonJS 和 AMD 的环境里都有一个 exports 变量,这个变量包含了一个模块的所有导出内容。

CommonJS 和 AMD 的 exports 都可以被赋值为一个 对象, 这种情况下其作用就类似于 EcmaScript 2015 语法里的默认导出,即 export default 语法了。虽然作用相似,但是 export default 语法并不能兼容 CommonJS 和 AMD 的 exports

为了支持 CommonJS 和 AMD 的 exports, TypeScript 提供了 export = 语法。

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

若使用 export = 导出一个模块,则必须使用 TypeScript 的特定语法 import module = require('module') 来导入此模块。

  • export = 只能导出 对象
  • export = 导出的模块只能用 import = require() 形式导入

文件 ZipCodeValidator.ts:

let numberRegexp = /^[0-9]+$/
class ZipCodeValidator {
  isAcceptable(s: string) {
    return s.length === 5 && numberRegexp.test(s)
  }
}
export = ZipCodeValidator

代码解释: 使用 export = 语法导出一个类对象。

文件 Test.ts:

import Zip = require('./ZipCodeValidator')

// Some samples to try
let strings = ['Hello', '98052', '101']

// Validators to use
let validator = new Zip()

// Show whether each string passed each validator
strings.forEach(s => {
  console.log(`'${ s }' - ${ validator.isAcceptable(s) ? 'matches' : 'does not match' }`)
});

代码解释: 通过 import = require() 形式导入。

6. 小结

可以看到 TypeScript 的模块机制基本采用的是 ES6 的内置模块化机制,另外添加了 export = 形式来兼容 AMD 与 CommonJS 规范。