interface 与 type 的终极对比
interface 与 type 的终极对比
一句话概括
interface 和 type 都能描述对象形状,但 interface 支持声明合并(开扩展点),type 能定义联合/交叉/元组(灵活组合)——选型不是喜好问题,是场景问题。默认 interface,需要联合/元组时用 type,库的类型定义优先 interface。
核心知识点
1. 对象定义能力 —— 几乎等价
1
2
3
4
5
6
7
8
9
10
11
// interface 写法
interface User { name: string; age: number }
// type 写法
type User = { name: string; age: number }
// 两者都能:
// - 被 class implements
// - 定义函数签名
// - 通过 extends / & 扩展
class Admin implements User { name = ''; age = 0 }
2. 声明合并 —— interface 独有的超能力
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 同名 interface 自动合并
interface User { name: string }
interface User { age: number }
// 合并后:{ name: string; age: number }
const u: User = { name: 'Alice', age: 30 }; // ✅
// 这才是 interface 的真正价值:给第三方库打补丁
declare interface Window {
__MY_CUSTOM_GLOBAL__: string; // 扩展 Window 类型,不覆盖原有定义
}
// type 重复定义直接报错
// type User = { age: number } // ❌ 重复定义
⚠️ 非函数成员类型冲突会报错。同名函数成员自动合并为函数重载。
3. type 能干的,interface 干不了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1. 原始类型别名
type ID = string;
// 2. 联合类型 —— type 的杀手锏
type Status = 'pending' | 'success' | 'error';
// 3. 元组
type Point = [number, number];
// 4. 交叉类型组合
type AdminUser = User & { role: 'admin'; permissions: string[] };
// 5. 从值提取类型
const config = { host: 'localhost', port: 3000 };
type Config = typeof config;
// 6. 映射类型 / 条件类型只能写 type
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type IsString<T> = T extends string ? true : false;
4. 扩展方式对比
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// interface 用 extends(可多继承)
interface A { a: string }
interface B { b: number }
interface C extends A, B { c: boolean }
// type 用 & 交叉
type A = { a: string }
type B = { b: number }
type C = A & B & { c: boolean }
// 两者可以互相扩展
interface D extends A { d: Date } // interface extends type ✅
type E = B & { e: Date } // type & interface ✅
// 关键差异:extends 遇到同名属性冲突会报错,& 交叉可能悄悄合并为 never
interface X { a: number }
interface Y extends X { a: string } // ❌ 直接报错 — 早暴露问题
type Z = X & { a: string } // ✅ 编译通过 — 但 a 的类型是 never(隐藏 bug)
5. 选型决策矩阵
| 场景 | 用 interface | 用 type |
|---|---|---|
| 定义对象/类形状 | ✅ 推荐 | ✅ |
| 需要声明合并(扩库的类型) | ✅ 唯一 | ❌ 做不到 |
| 联合类型、元组、primitive 别名 | ❌ 做不到 | ✅ |
| 映射类型 / 条件类型 | ❌ 做不到 | ✅ |
| 库的公开 API 类型 | ✅ 推荐(给用户开扩展点) | ❌ |
| React Props(带联合类型 variant) | 🟡 | ✅ |
其实你每天都在用
- 给 window 对象打补丁:
declare interface Window { __INITIAL_STATE__: State }—— 只有 interface 能做到不覆盖原有 Window - 组件 Props 用 type:
type ButtonProps = { variant: 'primary' \| 'secondary' } & ButtonHTMLAttributes—— 联合 + 交叉一把梭 - 三方库 .d.ts 全是 interface:为了让你通过声明合并扩展类型
- API 响应类型:
interface ApiResponse<T> { code: number; data: T }—— 对象形状,interface 语义更贴切 - 枚举值约束:
type Role = 'admin' \| 'editor' \| 'viewer'—— 这种联合类型只能用 type
常见误解
❌ 误区:「type 不能 extends,所以表达能力比 interface 弱」 type 用
&实现同样效果,而且&能组合联合类型、原始类型等 interface 根本表达不了的东西。type 表达能力强于 interface,只是少了声明合并。❌ 误区:「interface 和 type 性能有差别」 对 99.9% 的项目,两者编译性能差别可以忽略不计。除非你有几百层嵌套的递归类型。不要以性能为由选型。
❌ 误区:「React 官方用 interface 定义 Props」 React 官方源码的类型定义用的是
type。社区也没有统一标准——TS 官方文档的态度是:能描述清楚用哪个都行。❌ 误区:「交叉类型遇到冲突就报错,跟 extends 一样」 不一样。
extends遇到冲突直接报错(好!),&会把冲突属性合并为never(隐患:编译通过,但那个属性永远用不了)。这也是为什么定义对象形状推荐 interface。
一句话总结
一个简单的决策:这个类型需要被用户扩展(库的公开类型)→ interface;涉及联合类型、元组、映射类型 → type;只描述对象形状 → 团队约定优先,两者都能胜任。你不需要二选一,线上代码两个都用才是常态。
本文由作者按照 CC BY 4.0 进行授权