声明合并 -- Typescript基础篇(12)

172 阅读2分钟

在之前的函数章节中,通过声明多个函数类型实现函数的重载。函数重载就是函数声明合并。除了函数声明合并外。ts还会将接口和枚举声明进行合并。

接口声明合并

如果在同一模块中,声明多个相同的接口,接口会自动合并,并且保持属性:

interface User {
  name: string;
}

interface User {
  age: number;
}

// 等价于
interface User {
  name: string;
  age: number;
}

如果多个声明之间,有相同的属性(非函数),并且属性类型相同,则会报错;如果是函数属性,则会像函数重载合并该属性:

interface User {
  name: string;
}

// 属性类型相同
interface User {
  name: string;
}

// 属性不类型相同,报错
interface User {
  name: number;
}

// 函数重载
interface Cloner {
    clone(animal: Animal): Animal;
}

interface Cloner {
    clone(animal: Sheep): Sheep;
}

interface Cloner {
    clone(animal: Sheep): Sheep;
    clone(animal: Animal): Animal;
}

接口的函数成员在重载时,在同一个声明中的顺序保持不变;但后声明的函数顺序会在先声明的函数之上,即后声明函数的有更高的优先级。(如上面合并结果是clone(animal: Sheep): Sheep具备更高的优先级)

有一个例外,即函数参数中有一个参数是单个字符串字面量类型时,它会被冒泡到最顶部:

interface Document {
  createElement(tagName: any): any;
}
interface Document {
  createElement(tagName: "div"): any;
  createElement(tagName: "span"): any;
}
interface Document {
  createElement(tagName: string): any;
  createElement(tagName: "canvas"): any;
}

// 相当于
interface Document {
  createElement(tagName: "div"): any;
  createElement(tagName: "span"): any;
  createElement(tagName: "canvas"): any;
  createElement(tagName: string): any;
  createElement(tagName: any): any;
}

如果我们使用泛型接口,则泛型接口需要按照完全一样的方式声明,包括泛型名字;泛型约束也需要相同(如果部分声明使用约束,部分没有,则没有约束的声明默认使用已声明的约束):

interface User<T> {
  name: T;
}

// 泛型名字不同,报错
interface User<U> {
  name: U;
}


// 泛型约束相互不兼容,报错
interface User<T extends number> {
  name: T;
}

interface User<T extends string> {
  age: T;
}

// 泛型约束相同,所有的接口声明都继承于number
interface User<T extends number> {
  name: T;
}

interface User<T> {
  age: T;
}

虽然类型别名和接口大部分功能相似,但类型别名不具有声明合并,如果在同一个模块声明多个同名类型别名,ts会直接报错

枚举声明合并

枚举的声明合并与接口相似,但更为简单:

enum WeekDay {
  Mon,
  Tue,
  Wed,
}

enum WeekDay {
  Thu = 3, // 需要显式赋值
  Fri,
}

// 等价于
enum WeekDay {
  Mon,
  Tue,
  Wed,
  Thu,
  Fri,
}

合并多个枚举声明时,每个后续声明的第一个成员都需要声明初始值(至少需要为第一个成员赋值,也可以为每一个成员显式赋值)。

比较好的实践是为枚举的成员的每一项显式赋值,保证相互不覆盖。