1.接口 vs. 类型别名
接口创建了一个新的名字,可以在其它任何地方使用。 类型别名并不创建新名字—比如,错误信息就不会使用别名。 在下面的示例代码里,在编译器中将鼠标悬停在 interfaced
上,显示它返回的是 Interface
,但悬停在 aliased
上时,显示的却是对象字面量类型。另一个重要区别是类型别名不能被 extends
和 implements
(自己也不能 extends
和 implements
其它类型)。 因为 软件中的对象应该对于扩展是开放的,但是对于修改是封闭的,你应该尽量去使用接口代替类型别名。
类型别名相当于c里的宏,不会创建新的名字。而所谓的对于扩展是开放的,对于修改是封闭的只是对于面向对象的一个原则而已。因为如果修改了一个类或者模块,会导致其他引用其的模块发生变化,不易于维护。
2.映射类型
interface Person {
name?: string;
age?: number;
}
type Readonly<T> = {
readonly [P in keyof T]: T[P];
}
type Partial<T> = {
[P in keyof T]?: T[P];
}
type Nullable<T> = {
[P in keyof T]: T[P] | null ;
}
在这些例子里,属性列表是 keyof T
且结果类型是 T[P]
的变体。 这是使用通用映射类型的一个好模版。 因为这类转换是 同态的,映射只作用于 T
的属性而没有其它的。 编译器知道在添加任何新属性之前可以拷贝所有存在的属性修饰符。 例如,假设 Person.name
是只读的,那么 Partial<Person>.name
也将是只读的且为可选的。
如果映射的属性和原来的属性是同态的,即类型属性名一致,那么会拷贝所有存在的属性修饰符。
3.动态模块加载
这种模式的核心是import id = require("...")
语句可以让我们访问模块导出的类型。 模块加载器会被动态调用(通过 require
),就像下面if
代码块里那样。 它利用了省略引用的优化,所以模块只在被需要时加载。 为了让这个模块工作,一定要注意 import
定义的标识符只能在表示类型处使用(不能在会转换成JavaScript的地方)。
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("...")) { /* ... */ }
}
如果import定义的标识符旨在表示类型使用到,那么生成的js文件会把imort语句忽略掉。所以这个语句其实只是用来到处类型的。这有点像treeshake,这也是为什么amd模块需要用到import id=require("...")的原因吧。
4.模块扩展
你可以使用扩展模块来将它告诉编译器:
// observable.js
export class Observable<T> {
// ... implementation left as an exercise for the reader ...
}
// 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。 当这些声明在扩展中合并时,就好像在原始位置被声明了一样。 但是,你不能在扩展中声明新的顶级声明-仅可以扩展模块中已经存在的声明。
declare module中的接口会和模块扩展合并,就好像在原始的位置声明了这个接口一样。而不能在扩展中华声明新的顶级声明只能扩展模块中已经存在的声明则为了符合对面对象的六个原则。
5.三斜线指令
例如,把 /// <reference types="node" />
引入到声明文件,表明这个文件使用了@types/node/index.d.ts
里面声明的名字; 并且,这个包需要在编译阶段与声明文件一起被包含进来。
仅当在你需要写一个d.ts
文件时才使用这个指令。
对于那些在编译阶段生成的声明文件,编译器会自动地添加/// <reference types="..." />
; 当且仅当结果文件中使用了引用的包里的声明时才会在生成的声明文件里添加/// <reference types="..." />
语句。
三斜线type一般是用在自己写的d.ts中,用于引入声明的包,而至于自动生成的d.ts中(使用编译指令),则会自动的在编译过程中添加 /// <reference types="..." />,
只有结果文件当中用到了包声明才会在生成的声明文件中加上/// <reference types="..." />语句。