TypeScript$Concept-DeclarationMerging
identifier 标识符用来表示 something。
- 这个 something 在 JavaScript 中是变量 variable。(这里假设常量也是变量的一种😐)
- 在 TypeScript 中,这个 something 可以是 variable / type / namespace。
由于 variable / type / namespace 彼此不冲突,所以可以有同样的名字 identifier。(天堂、人间和地狱)Namespace 是值,可以扩展其他类型的值。
1. Stacking multiple things on an identifier
就像上面提到的,同一个 identifier 可以同时表示 variable、type & namespace。
const banana: Fruit = {
name: "banana",
color: "yellow",
mass: 183,
}
/// ---cut---
interface Fruit {
// ^?
name: string
mass: number
color: string
}
function Fruit(kind: string) {
// ^?
switch (kind) {
case "banana": return banana
default: throw new Error(`fruit type ${kind} not supported`)
}
}
// the namespace
namespace Fruit {
// ^?
function createBanana(): Fruit {
// ^?
return Fruit('banana')
// ^?
}
}
export { Fruit }
// ^?
/**
(alias) function Fruit(kind: string): Fruit
(alias) interface Fruit
(alias) namespace Fruit
export Fruit
*/
2. namespace
function $(selector: string): NodeListOf<Element> {
return document.querySelectorAll(selector)
}
namespace $ {
export function ajax(arg: {
url: string
data: any
success: (response: any) => void
}): Promise<any> {
return Promise.resolve()
}
}
namespace & class
class Album {
label: Album.AlbumLabel;
}
namespace Album {
export class AlbumLabel {}
}
namespace & function
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"));
namespace & enum
enum Color {
red = 1,
green = 2,
blue = 4,
}
namespace Color {
export function mixColor(colorName: string) {
if (colorName == "yellow") {
return Color.red + Color.green;
} else if (colorName == "white") {
return Color.red + Color.green + Color.blue;
} else if (colorName == "magenta") {
return Color.red + Color.blue;
} else if (colorName == "cyan") {
return Color.green + Color.blue;
}
}
}
3. class: value and type
- When
classis used as a type, it describes the type of an instance ofFruit - When
classis used as a value, it can both act as the constructor (e.g.,new Fruit()) and holds the “static side” of the class (createBanana()in this case)
除了 class 外,Enum 也是 value & type。
4. Module Augmentation
Although JavaScript modules do not support merging, you can patch existing objects by importing and then updating them.
// 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
};
This works fine in TypeScript too, but the compiler doesn’t know about Observable.prototype.map. You can use module augmentation to tell the compiler about it:
// 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
};
// consumer.ts
import { Observable } from "./observable";
import "./map";
let o: Observable<number>;
o.map((x) => x.toFixed());
However, there are two limitations to keep in mind:
- You can’t declare new top-level declarations in the augmentation — just patches to existing declarations.
- Default exports also cannot be augmented, only named exports (since you need to augment an export by its exported name, and
defaultis a reserved word - see #14080 for details)
Global augmentation
// observable.ts
export class Observable<T> {
// ... still no implementation ...
}
declare global {
interface Array<T> {
toObservable(): Observable<T>;
}
}
Array.prototype.toObservable = function () {
// ...
};