# 声明合并(Declaration Merging)

# 模块插件

已有模块

/*~ This example shows how to have multiple overloads for your function */
export interface GreeterFunction {
  (name: string): void
  (time: number): void
}
/*~ This example shows how to export a function specified by an interface */
export const greeter: GreeterFunction;
1
2
3
4
5
6
7

使用现有的模块

import { greeter } from "super-greeter";
// Normal Greeter API
greeter(2);
greeter("Hello world");

1
2
3
4
5

扩展现有的模块

/*~ On this line, import the module which this module adds to */
import { greeter } from "super-greeter";
/*~ Here, declare the same module as the one you imported above
 *~ then we expand the existing declaration of the greeter function
 */
export module "super-greeter" {
  export interface GreeterFunction {
    /** Greets even better! */
    hyperGreet(): void;
  }
}

1
2
3
4
5
6
7
8
9
10
11
12

在js中就可以使用扩展的方法,并且会有类型提示

import { greeter } from "super-greeter";
// Normal Greeter API
greeter(2);
greeter("Hello world");
// Now we extend the object with a new function at runtime
import "hyper-super-greeter";
greeter.hyperGreet();
1
2
3
4
5
6
7

# 合并interface

合并的时候interface的非函数成员应该是唯一的。如果它们不唯一,则它们必须属于同一类型。如果接口都声明同名但类型不同的非函数成员,编译器将发出错误。

正确
interface Box {
  height: number;
  width: number;
  info:{
    sex: number
  }
}
interface Box {
  scale: number;
}
let box: Box = { height: 5, width: 6, scale: 10, info:{sex:1} };

错误 ❌


interface Box {
  info: {
    age:number
  };
}
错误 ❌
interface Box {
  info: {
    age:number
    sex: number
  };
}

函数可以同名,同名就代表函数重载
interface Cloner {
  clone(animal: Animal): Animal;
}
interface Cloner {
  clone(animal: Sheep): Sheep;
}
interface Cloner {
  clone(animal: Dog): Dog;
  clone(animal: Cat): Cat;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 合并命名空间

与接口类似,同名的命名空间也会合并其成员。由于❗️❗️命名空间同时创建命名空间和值❗️❗️,因此我们需要了解两者如何合并。

为了合并命名空间,在每个命名空间中声明的导出接口的类型定义本身会被合并,形成一个内部包含合并的接口定义的命名空间。

要合并命名空间值,在每个声明站点,如果已存在具有给定名称的命名空间,则通过采用现有命名空间并将第二个命名空间的导出成员添加到第一个命名空间来进一步扩展该命名空间。

namespace Animals {
  export class Zebra {}
}
namespace Animals {
  export interface Legged {
    numberOfLegs: number;
  }
  export class Dog {}
}

相当于:
namespace Animals {
  export interface Legged {
    numberOfLegs: number;
  }
  export class Zebra {}
  export class Dog {}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

非导出成员仅在原始(未合并)命名空间中可见。这意味着合并后,来自其他声明的合并成员看不到非导出成员。

namespace Animal {
  let haveMuscles = true;
  export function animalsHaveMuscles() {
    return haveMuscles;
  }
}
namespace Animal {
  export function doAnimalsHaveMuscles() {
    return haveMuscles; // Error, because haveMuscles is not accessible here
  }
}
1
2
3
4
5
6
7
8
9
10
11

# 将命名空间与类、函数和枚举合并

命名空间足够灵活,还可以与其他类型的声明合并。 为此,命名空间声明必须位于它将与之合并的声明之后。 生成的声明具有两种声明类型的属性。

举例如下

将命名空间与类合并 这为用户提供了一种描述内部类的方法。

class Album {
  label: Album.AlbumLabel;
}
namespace Album {
    // 只有这里写了 export 上面才能使用  Album.AlbumLabel
  export class AlbumLabel {}
}
1
2
3
4
5
6
7

除了内部类的模式之外,您可能还熟悉创建函数然后通过向函数添加属性来进一步扩展函数的 JavaScript 实践。TypeScript 使用声明合并以类型安全的方式构建这样的定义。

function buildLabel(name: string): string {
  return buildLabel.prefix + name + buildLabel.suffix;
}
namespace buildLabel {
  export let suffix = "";
  export let prefix = "Hello, ";
}
console.log(buildLabel("Sam Smith"));
1
2
3
4
5
6
7
8

❗️❗️类不能与其他类或变量合并❗️❗️

# 模块增强

这在 TypeScript 中也能正常工作,但编译器不知道Observable.prototype.map. 您可以使用模块增强来告诉编译器:


// 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
};
1
2
3
4
5
6
7
8
9
10
// 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>;
  }
}
Observable.prototype.map = function (f) {
  // ... another exercise for the reader
};



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

可以在业务代码这样使用


import { Observable } from "./observable";
import "./map";
let o: Observable<number>;
o.map((x) => x.toFixed());

1
2
3
4
5
6

# 全局增强

您还可以从模块内部将声明添加到全局范围:

// observable.ts
export class Observable<T> {
  // ... still no implementation ...
}


declare global {
  interface Array<T> {
    toObservable(): Observable<T>;
  }
}
Array.prototype.toObservable = function () {
  // ...
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14