声明合并

声明合并是 TypeScript 将多个同名声明合并为一个定义的机制。声明合并让接口、命名空间等可以分散定义,然后自动合并。

接口合并

同名接口会自动合并。

interface User {
  id: number
  name: string
}

interface User {
  email: string
  age: number
}

const user: User = {
  id: 1,
  name: "张三",
  email: "zhangsan@example.com",
  age: 25
}

合并后的接口包含所有成员。

非函数成员

非函数成员必须唯一,否则类型必须相同。

interface User {
  id: number
  name: string
}

interface User {
  id: number
  email: string
}

如果 id 类型不同,会报错。

函数成员

函数成员会被重载。

interface Document {
  createElement(tag: string): HTMLElement
}

interface Document {
  createElement(tag: "canvas"): HTMLCanvasElement
  createElement(tag: "div"): HTMLDivElement
}

const canvas = document.createElement("canvas")
const div = document.createElement("div")

合并后的接口有多个重载签名。

命名空间合并

同名命名空间会合并。

namespace App {
  export interface User {
    id: number
    name: string
  }
}

namespace App {
  export interface Product {
    id: number
    name: string
    price: number
  }
}

const user: App.User = { id: 1, name: "张三" }
const product: App.Product = { id: 1, name: "商品", price: 99.99 }

合并后的命名空间包含所有导出成员。

命名空间与类合并

命名空间可以与类合并,类必须定义在命名空间之前。

class User {
  constructor(public name: string) {}
}

namespace User {
  export function create(name: string): User {
    return new User(name)
  }
}

const user = User.create("张三")
console.log(user.name)

合并后类有静态方法 create

命名空间与函数合并

命名空间可以与函数合并。

function greet(name: string): string {
  return `你好,${name}`
}

namespace greet {
  export function formal(name: string): string {
    return `尊敬的 ${name},您好`
  }
}

console.log(greet("张三"))
console.log(greet.formal("李四"))

合并后函数有静态方法 formal

命名空间与枚举合并

命名空间可以与枚举合并。

enum Status {
  Active,
  Inactive
}

namespace Status {
  export function isActive(status: Status): boolean {
    return status === Status.Active
  }
}

console.log(Status.isActive(Status.Active))
console.log(Status.isActive(Status.Inactive))

合并后枚举有静态方法 isActive

合并顺序

合并时,后面的声明优先级更高。

interface User {
  name: string
}

interface User {
  name: string
  email: string
}

合并后的接口以后面的定义为准。

实际应用

声明合并常用于扩展第三方库的类型。

interface Window {
  myApp: {
    version: string
  }
}

window.myApp = {
  version: "1.0.0"
}

console.log(window.myApp.version)

扩展 Window 接口添加自定义属性。

模块扩展

可以使用声明合并扩展模块。

import { User } from "./user"

declare module "./user" {
  interface User {
    age: number
  }
}

const user: User = {
  id: 1,
  name: "张三",
  email: "zhangsan@example.com",
  age: 25
}

扩展后的 User 接口包含 age 属性。

小结

声明合并将多个同名声明合并为一个定义。接口合并会合并所有成员,函数成员会形成重载。命名空间可以与类、函数、枚举合并。声明合并常用于扩展第三方库的类型。合理使用声明合并可以灵活地扩展类型定义。