总结
- 同步导出
-export { ZipCodeValidator as mainValidator }
重命名导出
-export * from "./StringValidator";
重新导出export { test } from "./Calculator";
-export default function (s: string) {...}
普通默认导出,标记为默认导出的类和函数的名字是可以省略的。 - 同步导入
-import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
重命名导入
-import * as validator from "./ZipCodeValidator";
整个模块导入
-import "./my-module.js";
直接导入,获取导出对象,直接创建关联关系,通常用于导入内部进行了某系操作的模块
-import validate, { other } from "./StaticZipCodeValidator";
普通导入 - 异步导入
- 可能是由于该文档比较旧,import(...)
是 ES6 用于支持异步导入的。在 TS 4.1.2 中支持这个写法 - 默认导入导出
- 非 ES6 的默认导入导出,export default
语法并不能兼容 CommonJS 和 AMD 的exports
。
-export = ZipCodeValidator;
非 ES6 的默认导出,若使用export =
导出一个模块,则必须使用 TypeScript 的特定语法import module = require("module")
来导入此模块。 - 特点
- 含有export
或import
的都是外部模块,也就是 ES6 中的模块。
- 模块中导出的引用类数据,一旦在模块外被修改,模块内的也会被修改。即,导入只是导入了该对象的引用。
- 只引入模块中的类型,TS 打包时不会打包这些模块。应该是有配置项可以配置引入类型时要写入typeof
关键字
- tsconfig 中的 include 声明的是 TS 在进行类型检查时需要包含的文件,即使这些文件没有真实的引用关系。
- 在 TS 中可以导入 .d.ts 文件,但是编译后如果没有对应的 JS 文件便会报模块丢失。可以在项目中的 .d.ts 文件导入这些外部声明,或者在 tsconfig 中配置 include 包含这些文件。 declare module
用于外部模块类型的声明
- 一个包含导入导出的 .d.ts 文件可以被认为是一个模块的类型
- 即使在declare module
已经写了实现,但依然只是类型的声明;
- 支持外部模块嵌入式扩展的声明扩展,例如class.prototype
的嵌入式扩展,可以使用declare module
扩展声明。
- 普通的模块扩展使用import
引入后再导出,实现实体和类型的同时扩展。
- 模块名为字符串,遵循模块路径的解析规则。会自动注册为路径对应模块的类型,相同名称的模块支持声明合并。即可以扩展一个 .d.ts 文件。
- 可以包含export
和export default
,表示该模块的导出的属性的类型和默认导出的类型。
-declare module "hot-new-module";
仅仅声明模块本身,模块内允许包含任意属性,这些属性的类型都是 any;
-declare module "*!text" {...}
声明一个可匹配一类模块的类型,它能够为import fileContent from "./xyz.txt!text";
提供类型- 在 .d.ts 文件中
export as namespace mathLib;
把该文件内的所有 export 作为一个命名空间,声明在全局有个 mathLib 对象。同时声明了该 .d.ts 对应的模块拥有的导出对象的类型。 - TS 中类型是通过搜集得到的,由 tsconfig 中的 include 标明,会搜集不具有导入导出的 .ts.d.ts。通过明文导入也会扩展类型搜集范围
- 在模块模式中当要声明全局变量时使用declare global
,或者说它叫所有模块注入变量会更准确点。
-declare var d3:string
在无导入导出文件中,声明一个全局变量 d3 的存在,并在他的类型是 string
-declare global
下的interface Window
用来扩展 window 对象 - 一个 ts 模块编译后应编译成两部分,一部分是由 JS 文件提供实现,一部分是由 .d.ts 提供的类型支持,供外部使用。
正文
原文地址 www.tslang.cn
关于术语的一点说明: 请务必注意一点,TypeScript 1.5 里术语名已经发生了变化。 “内部模块”现在称做 “命名空间”。 “外部模块” 现在则简称为“模块”,这是为了与 ECMAScript 2015 里的术语保持一致,(也就是说
module X {
相当于现在推荐的写法namespace X {
)。
介绍
从 ECMAScript 2015 开始,JavaScript 引入了模块的概念。TypeScript 也沿用这个概念。
模块使用模块加载器去导入其它的模块。 在运行时,模块加载器的作用是在执行此模块代码前去查找并执行这个模块的所有依赖。 大家最熟知的 JavaScript 模块加载器是服务于 Node.js 的 CommonJS 和服务于 Web 应用的 Require.js。
注释:AMD是模块规范,Require.js是这个规范的实现。
注释:这种全局可见需要配合<script>
标签引入,不然只是类型上的全局可见,无法访问实现。
TypeScript 与 ECMAScript 2015 一样,任何包含顶级import
或者export
的文件都被当成一个模块。相反地,如果一个文件不带有顶级的import
或者export
声明,那么它的内容被视为全局可见的(因此对模块也是可见的)。
导出
导出声明
注释:在 TS 中,类型和实现都可以作为模块的导入和导出使用
任何声明(比如变量,函数,类,类型别名或接口)都能够通过添加export
关键字来导出。
Validation.ts
export interface StringValidator {
isAcceptable(s: string): boolean;
}
ZipCodeValidator.ts
export const numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
导出语句
导出语句很便利,因为我们可能需要对导出的部分重命名,所以上面的例子可以这样改写:
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";
或者一个模块可以包裹多个模块,并把他们导出的内容联合在一起通过语法:export * from "module"
。
AllValidators.ts
export * from "./StringValidator"; // exports interface StringValidator
export * from "./LettersOnlyValidator"; // exports class LettersOnlyValidator
export * from "./ZipCodeValidator"; // exports class ZipCodeValidator
导入
导入一个模块中的某个导出内容
可以对导入内容重命名
import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
let myValidator = new ZCV();
将整个模块导入到一个变量,并通过它来访问模块的导出部分
import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();
具有副作用的导入模块
尽管不推荐这么做,一些模块会设置一些全局状态供其它模块使用。 这些模块可能没有任何的导出或用户根本就不关注它的导出。 使用下面的方法来导入这类模块:
注释:没有任何导出的模块即使通过
import
也不会把模块内的所有变量全局注册。和这句有冲突?
import "./my-module.js";
默认导出
类和函数声明可以直接被标记为默认导出。 标记为默认导出的类和函数的名字是可以省略的。
StaticZipCodeValidator.ts
const numberRegexp = /^[0-9]+$/;
export default function (s: string) {
return s.length === 5 && numberRegexp.test(s);
}
Test.ts
import validate from "./StaticZipCodeValidator";
let strings = ["Hello", "98052", "101"];
// Use function validate
strings.forEach(s => {
console.log(`"${s}" ${validate(s) ? " matches" : " does not match"}`);
});
default
导出也可以是一个值
OneTwoThree.ts
export default "123";
Log.ts
import num from "./OneTwoThree";
console.log(num); // "123"
export = 和 import = require()
CommonJS 和 AMD 的环境里都有一个exports
变量,这个变量包含了一个模块的所有导出内容。
CommonJS 和 AMD 的exports
都可以被赋值为一个对象
, 这种情况下其作用就类似于 es6 语法里的默认导出,即 export default
语法了。虽然作用相似,但是 export default
语法并不能兼容 CommonJS 和 AMD 的exports
。
为了支持 CommonJS 和 AMD 的exports
, TypeScript 提供了export =
语法。
export =
语法定义一个模块的导出对象
。 这里的对象
一词指的是类,接口,命名空间,函数或枚举。
若使用export =
导出一个模块,则必须使用 TypeScript 的特定语法import module = require("module")
来导入此模块。
ZipCodeValidator.ts
let numberRegexp = /^[0-9]+$/;
class ZipCodeValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
export = ZipCodeValidator;
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" }`);
});
简单示例
注释:以下是一个编译 TS 文件为目标文件的示例
下面我们来整理一下前面的验证器实现,每个模块只有一个命名的导出。
为了编译,我们必需要在命令行上指定一个模块目标。对于 Node.js 来说,使用--module commonjs
; 对于 Require.js 来说,使用--module amd
。比如:
tsc --module commonjs Test.ts
编译完成后,每个模块会生成一个单独的.js
文件。 好比使用了 reference 标签,编译器会根据 import
语句编译相应的文件。
可选的模块加载和其它高级加载场景
注释:在书写时只引入了该模块的类型,不会打包这个引入的模块。TS 可以根据需要在引用到时才按需引入,CommonJS 虽然是同步的但也有按需的同步引入
有时候,你只想在某种条件下才加载某个模块。 在 TypeScript 里,使用下面的方式来实现它和其它的高级加载场景,我们可以直接调用模块加载器并且可以保证类型完全。
编译器会检测是否每个模块都会在生成的 JavaScript 中用到。 如果一个模块标识符只在类型注解部分使用,并且完全没有在表达式中使用时,就不会生成 require
这个模块的代码。 省略掉没有用到的引用对性能提升是很有益的,并同时提供了选择性加载模块的能力。
注释:
import id = require("...")
能够实现按需引入,下文应该是有误的,这个方法不是为了访问模块导出的类型。
注释:由于 import 是同步引入,为了把这部分打包成按需引入所以只允许使用 import 引入的类型,为了确保安全应该加添typeof
关键字(有校验配置项可以设置为:如果引入只是使用类型的话 import 上必须加上 typeof)
注释:在 4.1.2 版本中支持import("...")
这种 ES6 的异步加载语法
这种模式的核心是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
文件。 让我们看一些例子。
外部模块
注释:module 后的名称在 TS 中进行了全局注入。
在 Node.js 里大部分工作是通过加载一个或多个模块实现的。 我们可以使用顶级的 export
声明来为每个模块都定义一个.d.ts
文件,但最好还是写在一个大的.d.ts
文件里。 我们使用与构造一个外部命名空间相似的方法,但是这里使用 module
关键字并且把名字用引号括起来,方便之后import
。 例如:
node.d.ts (simplified excerpt)
注释:这里不是一个全局注册,他同样需要引用才能使用 url,只是这种写法不会出现 url 的引用提醒,只会出现它包含的 Url 接口的。等同于定义了一个内部模块,这个模块不被导出,只有模块内的方法或属性被导出了。
注释:这里是一个 .d.ts 文件,这里的function parse
不是标准类型写法,在 .ts 中会报缺乏实体定义。下文的let sep
也相同,应该可以理解为在 .d.ts 文件中可以书写实体,但是不需要写实体的实现。
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;
}
注释:下文的
<reference>
标签用于标明当前模块的依赖模块
现在我们可以/// <reference>
node.d.ts
并且使用import url = require("url");
或import * as URL from "url"
加载模块。
/// <reference path="node.d.ts"/>
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 内容。 它们通常会使用一个前缀或后缀来表示特殊的加载语法。 模块声明通配符可以用来表示这些情况。
注释:通配符可以实现对一个类型的模块进行统一的类型定义
蛛丝:模块内可以写 expor default 作为整个模块的导出名
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 模块为代表。 这些库可以通过导入的形式或全局变量的形式访问。 例如:
注释:export as 使模块不仅能够被导入使用,也提供了全局注册使用的方式,但是这两个方式之前并不兼容。可以理解为函数的重载,只是一个模块有两种使用方式
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);
创建模块结构指导
尽可能地在顶层导出
从你的模块中导出一个命名空间就是一个增加嵌套的例子。
使用重新导出进行扩展
模块不会像全局命名空间对象那样去 合并。
模块里不要使用命名空间
模块具有其自己的作用域,并且只有导出的声明才会在模块外部可见。 记住这点,命名空间在使用模块时几乎没什么价值。
模块本身已经存在于文件系统之中,这是必须的。 我们必须通过路径和文件名找到它们,这已经提供了一种逻辑上的组织形式。 我们可以创建 /collections/generic/
文件夹,把相应模块放在这里面。
命名空间对解决全局作用域里命名冲突来说是很重要的。 比如,你可以有一个 My.Application.Customer.AddForm
和My.Application.Order.AddForm
-- 两个类型的名字相同,但命名空间不同。
更多关于模块和命名空间的资料查看命名空间和模块
危险信号
以下均为模块结构上的危险信号。重新检查以确保你没有在对模块使用命名空间:
- 文件的顶层声明是
export namespace Foo { ... }
(删除Foo
并把所有内容向上层移动一层) - 文件只有一个
export class
或export function
(考虑使用export default
) - 多个文件的顶层具有同样的
export namespace Foo {
(不要以为这些会合并到一个Foo
中!)
注释:在 4.1.2 中,两个
export
的命名空间会自动合并
——————————————————————————————————————————————————————
以下内容来源于 声明合并 原文地址 www.tslang.cn
模块扩展
注释:被拓展的目标必须是该目标的定义所在,像
export * from "./StringValidator";
这样的重新导出,不存在该目标的定义是不能够拓展的。
虽然 JavaScript 不支持合并,但你可以为导入的对象打补丁以更新它们。让我们考察一下这个玩具性的示例:
// observable.js
export class Observable<T> {
// ... implementation left as an exercise for the reader ...
}
// map.js
import { Observable } from "./observable";
Observable.prototype.map = function (f) {
// ... another exercise for the reader
}
它也可以很好地工作在 TypeScript 中, 但编译器对 Observable.prototype.map
一无所知。 你可以使用扩展模块来将它告诉编译器:
// observable.ts stays the same
// map.ts
import { Observable } from "./observable";
declare module "./observable" {
interface Observable<T> {
map<U>(f: (x: T) => U): Observable<U>;
}
}
Observable.prototype.map = function (f) {
// ... another exercise for the reader
}
// consumer.ts
import { Observable } from "./observable";
import "./map";
let o: Observable<number>;
o.map(x => x.toFixed());
模块名的解析和用 import
/ export
解析模块标识符的方式是一致的。 更多信息请参考 Modules。 当这些声明在扩展中合并时,就好像在原始位置被声明了一样。 但是,你不能在扩展中声明新的顶级声明-仅可以扩展模块中已经存在的声明。
全局扩展
你也以在模块内部添加声明到全局作用域中。
// observable.ts
export class Observable<T> {
// ... still no implementation ...
}
declare global {
interface Array<T> {
toObservable(): Observable<T>;
}
}
Array.prototype.toObservable = function () {
// ...
}
全局扩展与模块扩展的行为和限制是相同的。
——————————————————————————————————————————————————————
以下内容来源于 命名空间和模块 原文地址 www.tslang.cn
模块的取舍
就像每个 JS 文件对应一个模块一样,TypeScript 里模块文件与生成的 JS 文件也是一一对应的。 这会产生一种影响,根据你指定的目标模块系统的不同,你可能无法连接多个模块源文件。 例如当目标模块系统为 commonjs
或umd
时,无法使用outFile
选项,但是在 TypeScript 1.8 以上的版本能够使用outFile
当目标为amd
或system
。
注释:UMD 是 AMD、CommonJs、CMD 的融合写法,使一个模块能同时被三种加载方式加载
注释:CMD 和 AMD 的不同在于 CMD 在需要依赖时执行依赖,而 AMD 的依赖前置到模块前。Seajs 是 CMD 的实现。
疑问:UMD 的实现是哪个库?system 是一种新的模块加载方案?和 system.js 是什么关系?