TypeScript$Concept-DeclarationMerging

87 阅读1分钟

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 class is used as a type, it describes the type of an instance of Fruit
  • When class is 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:

  1. You can’t declare new top-level declarations in the augmentation — just patches to existing declarations.
  2. Default exports also cannot be augmented, only named exports (since you need to augment an export by its exported name, and default is 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 () {
  // ...
};

Links

TypeScrioptDeclarationMerging

CourseDeclarationMerging