在没有使用模块化编程的时代,会经常遇到全局变量污染、变量重名、多个文件之间存在依赖关系,需要保证一定加载顺序等问题。在模块化这种规范被提出后,得到社区和广大开发者的积极响应。
本节将介绍 TypeScript 的模块化方案,学习模块的导入导出机制,要注意 TypeScript 是怎么样兼容 CommonJS 和 AMD 规范的。
模块在其自身的作用域里执行,而不是在全局作用域里。
export
: 导出模块中的变量、函数、类、接口等;
import
: 导入其他模块导出的变量、函数、类、接口等。
TypeScript 与 ECMAScript 2015 一样,任何包含顶级 import
或者 export
的文件都被当成一个模块。相反的,如果一个文件不带有顶级的 import
或者 export
声明,那么它的内容被视为全局可见的。
在一个 TypeScript 工程创建一个 test.ts
文件,写入代码:
const a = 1
然后,在相同的工程下创建另一个 test2.ts
文件,写入代码:
const a = 2
此时编译器会提示重复定义错误,虽然是在不同的文件下,但处于同一全局空间。
如果加上 export
导出语句:
export const a = 1
这样,两个 a
因处于不同的命名空间,就不会报错。
任何声明(比如变量,函数,类,类型别名或接口)都能够通过添加 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
关键字导出。
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
关键字导出。
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
关键字将声明重命名。
重新导出功能并不会在当前模块导入那个模块或定义一个新的局部变量。
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'
export default class ZipCodeValidator {
static numberRegexp = /^[0-9]+$/
isAcceptable(s: string) {
return s.length === 5 && ZipCodeValidator.numberRegexp.test(s)
}
}
代码解释: 每个模块都可以有一个 default
导出,且一个模块只能够有一个 default
导出。
使用 import
形式来导入其它模块中的导出内容。
import { a, add, Employee } from './export'
import { a as level, used as status } from './export'
将整个模块导入到一个变量,并通过它来访问模块的导出部分
import * as TYPES from './export'
import './export'
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()
形式导入。
可以看到 TypeScript 的模块机制基本采用的是 ES6 的内置模块化机制,另外添加了 export =
形式来兼容 AMD 与 CommonJS 规范。