1. 接口合并
在typescript,想要描述一个对象的类型。一般通过2种方式:
- 使用type声明一个类型别名
- 使用interface声明一个接口
上述2种方式在使用上几乎可以相互替换。最主要的一点不同在于type不能进行声明合并,而interface可以进行声明合并。如下:
interface Animal {}
interface Sheep {}
interface Dog {}
interface Cat {}
interface Cloner {
clone(animal: Animal): Animal;
print():void
}
interface Cloner {
clone(animal: Sheep): Sheep;
}
interface Cloner {
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
}
interface Cloner { // 合并后的结果
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
clone(animal: Sheep): Sheep;
clone(animal: Animal): Animal;
print():void
}
简单来说,就是同名接口上的所有成员全部都被合并到一个接口内部,并遵循以下规则:
- 同名方法,变为重载
- 同名属性必须类型一致
这儿还有一个隐式规则,就是后定义的接口,优先级最高。比如上述示例,第三个Cloner接口的优先级最高,其clone方法在合并接口里的位置是在最顶端。
2. 命名空间合并
-
命名空间之间的合并
简洁的说,就是将2个命名空间内部的的类型声明以及值合并到一起,此时需要遵守变量“单一声明”原则,比如class不能重复定义。这个地方可以这样理解,当我们编写不带命名空间的代码时,默认处于default命名空间下,此时需要遵守的变量声明原则,和命名空间合并保持一致。
-
命名空间与class,function,enum的合并
命名空间的合并是灵活的,其可以与class,function,enum进行合并,当采用这类型的合并时,同时兼并命名空间和指定合并类型的特性。
-
与class合并
class Demo { name="Demo" } namespace Demo { export class A { name= "A" } } let demo = new Demo() let demoA = new Demo.A() -
与function合并
function Demo(){ console.log('demo') } namespace Demo { let a = 'a' export function doA(){ console.log(a) } } Demo() Demo.doA() -
与enum合并
enum Demo { First, Second } namespace Demo { let a = 'a' export function doA(){ console.log(a) } } console.log(Demo.First) console.log(Demo.doA)
-
3. 模块扩展
虽然javascript的模块不支持合并,但依旧可以引入已经存在的对象,并进行修改,简单示例如下:
// observable.ts
export class Observable<T> {
// ... implementation left as an exercise for the reader ...
}
// map.ts
import { Observable } from "./observable";
Observable.prototype.map = function (f) {
// ... another exercise for the reader
};
通过上面的方式,依然可以对class类进行扩展,新增一个map方法。但这种方式在typescript中不会得到正确的类型推导,即typescript并不知道Observable类拥有map方法。那么此时就可以使用模块扩展,即扩展Observable的声明定义,语法:declare module modulePath。如下:
// observable.ts
export class Observable<T> {
// ... implementation left as an exercise for the reader ...
}
// map.ts
import { Observable } from "./observable";
declare module "./observable" {
interface Observable<T> {
map<U>(f: (x: T) => U): Observable<U>;
}
}
通过类型扩展,此时typescript就会知道Observable类拥有一个map方法,类型扩展必须遵顼2个原则:
- 不能扩展一个新的顶层声明,即只能针对已经存在的类型声明进行扩展。比如在上述模块文件./observable.ts中,只导出了一个Observable,则此时不能新增一个NewObservable的扩展声明。
- 默认导出不能被扩展。因为扩展需要基于一个导出名称。
4. 全局扩展
全局扩展和模块扩展在特性上保持一致。只在语法上略有区别,如下:
// observable.ts
export class Observable<T> {
// ... still no implementation ...
}
declare global {
interface Array<T> {
toObservable(): Observable<T>;
}
}
Array.prototype.toObservable = function () {
// ...
};
语法:declare global。上述示例即在内置类型Array上新增了一个toObservable的方法。