你是不是也有过这样的困惑:
打开一个老项目,或者在做 Code Review 时,发现代码里一会儿是 interface Props,一会儿又是 type State。问同事为什么要混用,他也支支吾吾说不出个所以然,最后只能来一句:"哎呀,反正都能跑,看心情呗。"
但在 TypeScript 的世界里,"能跑"和"写得好"是两码事。
type(类型别名)和 interface(接口)这对双胞胎,在 TS 诞生之初就一直相爱相杀。它们确实太像了,像到在绝大多数 CRUD 业务开发中,你闭着眼随便选一个都不会报错。
但是,作为一个追求代码质量的开发者,我们不能止步于此。
今天,我们跳出表面的语法糖,从底层机制入手,彻底搞清楚它们的本质区别,并给你一套最佳实践方案。拒绝选择困难症。
1. 表象:90% 的重合度与其误区
为什么大家会纠结?因为在定义"对象"的形状(Shape)时,它们长得几乎一模一样。
看看下面的代码,你能一眼看出区别吗?
typescript
// 使用 interface
interface UserI {
name: string;
age: number;
}
// 使用 type
type UserT = {
name: string;
age: number;
}
在日常使用中,如果你想实例化一个对象,或者在函数参数中约束类型,这俩兄弟的表现是完全一致的。它们都支持:
- 定义对象结构
- 定义函数签名
- 支持泛型
- 支持类(Class)的实现(implements)
但这正是新手容易陷入的误区:以为它们是可以随意互换的同义词。 实际上,TS 设计这两个概念,是为了解决完全不同的问题。
2. 核心:声明合并、类型表达与扩展性
区别不仅存在,而且在关键时刻决定了你的架构设计是否合理。主要体现在以下三个核心维度:
2.1 声明合并 (Declaration Merging) ------ Interface 的必杀技
这是 interface 独有的特性,也是它存在的最大理由。
场景模拟:
你引入了一个第三方库(比如 Vue 或 jQuery),但你发现它的全局对象上少了一个你需要的属性。这时,如果你用 interface,你可以直接在自己的代码里"补"上这个属性。
kotlin
// 假设这是第三方库定义的 interface
interface User {
name: string;
}
// 你的代码中再次定义同名 interface
interface User {
age: number;
}
// ✨ TS 会自动把它们缝合在一起!
const me: User = {
name: "Gemini",
age: 18 // 必须两个属性都有,否则报错
};
反观 type:
它是封闭的(Closed)。一旦定义,无法通过同名方式修改。
ini
type User = {
name: string;
};
// ❌ 报错:Duplicate identifier 'User'.
type User = {
age: number;
};
💡 结论: interface 具有开放性,允许后续扩展;而 type 具有封闭性,更适合确定的业务逻辑。
2.2 类型表达能力 ------ Type 的主场
type 的全称是 Type Alias(类型别名) 。既然是别名,它就能给任何东西起名字,不仅仅是对象。
在处理复杂类型时,Type 的灵活性完胜 Interface:
-
联合类型 (Union Types): 前端开发中最常用的功能。
initype Status = 'pending' | 'success' | 'failed'; type ID = string | number;Interface 无法直接定义这种"或"的关系。
-
元组 (Tuple):
initype Point = [number, number]; -
类型体操:
当你使用 Pick、Omit、Record 或者条件类型(Conditional Types)时,产出的结果通常都是 type。
2.3 扩展方式:Extends vs Intersection
虽然两者都能实现"继承"的效果,但语义不同。
- Interface 使用
extends:侧重于面向对象的层级继承。 - Type 使用
&(交叉类型) :侧重于集合的合并。
虽然通常可以互通,但在处理冲突属性时,interface 会直接报错提醒,而交叉类型(&)可能会产生 never 类型,导致错误提示不够直观。
3. 规范:一套拿来即用的最佳实践
讲了这么多理论,回到最初的问题:我们在项目中到底该怎么选?
与其每次都纠结,不如遵循这套简单的 "二选一法则" ,这也符合目前主流大厂(如 Google 规范)和 React 社区的推荐趋势:
场景一:你在编写库 (Library) 或第三方包
请优先使用 interface。
理由: 作为库的作者,你需要为你的用户留出"后路"。用户可能需要利用"声明合并"的特性,向你的全局接口中注入自定义属性(比如扩展 Window 对象或给 Request 对象增加 user 字段)。使用 Interface 是对使用者的尊重。
场景二:你在编写业务应用 (Application / UI 组件)
请优先使用 type。
理由:
- 一致性 (Consistency): 既然
type能搞定对象、联合类型、元组等所有情况,而interface只能搞定对象,那么全员使用type可以让代码风格更统一。 - 安全性 (Safety): 在业务代码中,我们通常不希望定义好的类型被莫名其妙地"自动合并"了(这是隐患)。
type的报错提醒能让你更安全。 - React 生态: 现在的 React 社区更倾向于用
type来定义Props和State,因为它在处理组件复合类型时更加直观。
总结
为了方便记忆,我做了一张对比速查表:
| 特性 | Interface | Type |
|---|---|---|
| 核心理念 | 描述对象的形状 (Shape) | 任何类型的别名 (Alias) |
| 声明合并 | ✅ 支持 (自动合并) | ❌ 不支持 (会报错) |
| **联合类型 ( | )** | ❌ 不支持 |
| 映射/条件类型 | ❌ 不支持 | ✅ 支持 |
| 最佳使用场景 | 编写库 (Library) | 编写应用 (App) |
一句话口诀:
对外 API(库)用 Interface,对内业务逻辑用 Type。如果你实在拿不准,就用 Type,直到你必须用 Interface 为止。
你们团队的代码规范里,是强制用 type 还是 interface?还是像大部分项目一样"随缘混用"?
欢迎在评论区留言,我们一起聊聊 TS 里的那些坑!