11.接口 (Interfaces) 副本.md 来源:https://my.feishu.cn/file/Vz4RbkVLeoj1jcxkt6VcX0C6nah 采集状态:已完成正文采集;已从查看代码模式逐行提取,行号 1 至 146 连续,并核对滚动到底。 在 TypeScript 的世界里,如果说类型别名 (`type`) 是一位灵活的魔术师,能为各种类型赋予新名字,那么**接口 (`interface`)** 则更像是一位严谨的建筑师,专门为对象的结构蓝图绘制契约。 接口是 TypeScript 核心概念之一,也是其面向对象特性的重要基石。它允许我们定义一个“契约”,任何被该契约“标记”的对象或类都必须遵守其规定。这种强制性的约定,是构建大型、可维护和可协作应用的关键。 ### 什么是接口? 接口的核心任务是**定义一个对象应该具有的形状**。它描述了对象必须包含哪些属性,以及这些属性的类型。任何试图“扮演”这个角色的对象,都必须严格遵守这份契约。 其基本语法非常直观: ```typescript interface User { readonly id: number; username: string; email: string; avatarUrl?: string; // 可选属性 } ``` 这个 `User` 接口定义了一个清晰的契约: * 一个 `User` 对象必须有一个只读的 `id` 属性,类型为 `number`。 * 必须有一个 `username` 和 `email` 属性,类型为 `string`。 * 可以有一个可选的 `avatarUrl` 属性,如果存在,其类型必须是 `string`。 我们可以像使用其他类型一样使用这个接口: ```typescript function displayUser(user: User): void { console.log(`Username: ${user.username}`); console.log(`Email: ${user.email}`); } const myUser: User = { id: 1, username: "raycast", email: "hello@raycast.com", }; displayUser(myUser); // 合法 // const invalidUser = { id: 2, username: "ai" }; // 编译时错误: Property 'email' is missing in type '{ id: number; username: string; }' // but required in type 'User'. ``` 编译器会确保任何传递给 `displayUser` 函数的参数,或者任何声明为 `User` 类型的变量,都严格遵守 `User` 接口所定义的结构。 ### 接口的扩展:`extends` 接口最强大的特性之一是其可扩展性。一个接口可以通过 `extends` 关键字来“继承”另一个接口的成员,从而构建出更复杂的类型结构。这在面向对象编程中是非常常见的模式。 假设我们要在 `User` 的基础上定义一个 `Admin` 用户,它除了拥有普通用户的所有属性外,还有一个额外的 `level` 属性。 ```typescript interface Admin extends User { level: 'super' | 'editor' | 'viewer'; } const myAdmin: Admin = { id: 2, username: "admin_user", email: "admin@raycast.com", level: "super", }; displayUser(myAdmin); // 合法, 因为 Admin 是 User 的一种 ``` 通过 `extends User`,`Admin` 接口自动继承了 `User` 的所有属性(`id`, `username`, `email`, `avatarUrl?`),并在此基础上添加了自己的 `level` 属性。这种方式极大地促进了代码的复用和层次化设计。 ### `interface` vs. `type`: 终极对决 这是 TypeScript 开发者最常讨论的话题之一。`interface` 和 `type` 在定义对象形状方面有很多重叠之处,但它们的设计哲学和关键特性有所不同。 | 特性 | `interface` (接口) | `type` (类型别名) | | :--------------- | :---------------------------------------------------------------------------------- | :------------------------------------------------------------------------- | | **核心用途** | 专门用于定义对象、类、函数的“契约”或“形状” | 为任何 TypeScript 类型创建别名,用途更广泛 | | **扩展方式** | 使用 `extends` 关键字,清晰地表达继承关系 | 使用交叉类型 `&` 来组合类型 | | **声明合并** | **支持**。多个同名 `interface` 会自动合并,这对于扩展第三方库的类型非常有用。 | **不支持**。创建同名的 `type` 会直接导致编译错误。 | | **适用范围** | 主要用于对象结构 | 可以是联合类型 (`string \| number`)、元组 (`[string, number]`) 等任何类型 | #### 关键区别 1:声明合并 (Declaration Merging) 这是两者之间最本质的区别。你可以多次声明同一个 `interface`,TypeScript 会将它们的属性合并在一起。 ```typescript // 在文件 a.ts 中 interface Window { title: string; } // 在文件 b.ts 中 (同一个项目) interface Window { raycastApi: object; } // TypeScript 会将它们合并为: // interface Window { // title: string; // raycastApi: object; // } window.title = "My App"; window.raycastApi = { /* ... */ }; ``` 这个特性使得 `interface` 成为扩展现有 JavaScript 对象(如 `window`)或为第三方库添加自定义属性时的理想选择。而 `type` 别名则不允许这样做,保证了类型的唯一性。 #### 关键区别 2:表达能力 `type` 别名的表达能力更广泛。它不仅能定义对象,还能为联合类型、元组、交叉类型等任何类型创建别名,这是 `interface` 无法做到的。 ```typescript // 联合类型 type Status = "success" | "pending" | "failed"; // 元组 type PointData = [number, number]; // 无法用 interface 实现 // interface Status = "success" | "pending" | "failed"; // 错误! ``` ### 何时使用 `interface`?何时使用 `type`? 根据社区的最佳实践,我们可以遵循以下准则: 1. **优先使用 `interface` 来定义对象和类的结构**。 * 当你需要定义一个明确的对象“形状”,并且期望它可能被其他接口 `extends` 或被类 `implements` 时,`interface` 是更符合语义和面向对象思想的选择。 * 当你需要利用声明合并来扩展一个已有的接口时(例如扩展第三方库的类型定义),`interface` 是唯一的选择。 2. **在需要更灵活的类型组合时,使用 `type`**。 * 当你需要定义联合类型、元组、或映射类型等非对象结构时,必须使用 `type`。 * 当你想为一个已有类型(即使是原始类型)创建一个清晰的别名时,`type` 是最直接的方式。 一个广为流传的经验法则是:“**用 `interface` 直到你需要 `type` 的特性为止**”。这鼓励我们默认使用 `interface` 来保持代码风格的一致性,只在遇到 `interface` 无法处理的场景(如联合类型)时才切换到 `type`。 ### 总结 接口是 TypeScript 类型系统的支柱,它为代码带来了“契约精神”,使得大规模协作和长期维护成为可能。 * `interface` 是定义对象和类结构蓝图的首选工具,它通过 `extends` 关键字支持清晰的继承关系。 * 其独特的“声明合并”特性,使其在扩展已有类型定义时无可替代。 * 虽然 `type` 在某些方面更为灵活,但在定义核心业务对象的“形状”时,`interface` 提供了更符合面向对象思想的语义和实践。 深刻理解 `interface` 和 `type` 的异同,并根据场景做出明智的选择,是每一位 TypeScript 开发者迈向更高阶水平的必经之路。