编程全栈开发人员

什么是 TypeScript 中的声明合并,它在实践中如何工作?

用 Hintsage AI 助手通过面试

回答。

问题的背景

声明合并是 TypeScript 的独特特性,允许将多个同名的声明合并为一个实体。这与 TypeScript 作为 JavaScript 上的类型化的历史有关:许多第三方库声明了接口、函数、命名空间,而 TypeScript 必须允许在不修改库的源代码的情况下扩展它们。

问题

在复杂的 API 中以及对第三方 JS 库进行类型化时,可能需要分配责任——例如,扩展模块的类型、添加接口字段、合并名称。然而,大多数语言不支持这样的声明合并。

解决方案

TypeScript 允许合并同名的接口、命名空间、函数、类的声明,这使得 API 在扩展时更具灵活性。它用于扩展第三方类型、向库添加自定义方法,以及组织模块化代码。

代码示例:

// 接口合并 interface User { id: number; } interface User { name: string; } const u: User = { id: 1, name: "Jack" }; // 命名空间 + 函数合并 function greet() { return "Hi!"; } namespace greet { export function loud() { return "HI!"; } } greet(); // "Hi!" greet.loud(); // "HI!"

关键特性:

  • 允许合并接口、带有命名空间的函数、带有命名空间的枚举,但不支持类型。
  • 用于描述可扩展的 API 和全局声明的扩展。
  • 允许安全地修改/扩展第三方库的类型,而无需修改它们。

让人思考的问题。

可以将 type alias 像接口那样合并吗?

不可以,type alias 不能合并。尝试声明多个具有相同名称的 type 会导致编译错误。

type T = { a: string }; type T = { b: number }; // 错误

TypeScript 会以随机顺序插入接口/命名空间字段吗?

合并的结构总是按声明顺序构建——如果存在相同属性名称,则最后的声明“获胜”。

接口的方法会合并为一个函数吗?

不会,具有相同名称的方法在不同接口中不会合并为一个函数——如果签名匹配,TypeScript 仍然不允许实现“两个”变体。

常见错误和反模式

  • 尝试合并不带命名空间的 type alias 或枚举——将导致错误。
  • 接口内具有不同类型的重复字段——会引发冲突,无法合并;
  • 在没有明确需求的情况下使用合并——导致代码难以阅读。

现实案例

负面案例

公司两次定义全局接口 Window,其字段不同且同名字段的类型也不同。在构建过程中,构建工具没有发现问题,但在运行时会出现意外的类型冲突。

优点:

  • 快速“扩展”接口而不改变源代码。

缺点:

  • 冲突的名称会导致 bug,难以查找。
  • 通常会加大理解类型完整结构的难度。

正面案例

为第三方库编写 d.ts 文件,API 接口在不改变库本身的情况下由单独模块扩展,所有扩展都有文档说明,命名策略在 Wiki 中描述。

优点:

  • 安全地扩展第三方 API。
  • 可以逐步进行改进和补充。

缺点:

  • 需要维护相应的文档和命名规范。
  • 在没有严格命名政策的大团队中,冲突的风险增加。