两者都能描述对象,但能力并不等价。选择标准不是"哪个更好",而是"你要用哪个能力"。
能力对比
1// interface 与 type 的能力对比
2
3// ---- 都能做的 ----
4interface I1 { a: number }
5type T1 = { a: number };
6
7// 扩展
8interface I2 extends I1 { b: string }
9type T2 = T1 & { b: string };
10
11// 函数类型
12interface Fn1 { (x: number): string }
13type Fn2 = (x: number) => string;
14
15// 泛型
16interface G1<T> { value: T }
17type G2<T> = { value: T };
18
19// 索引签名
20interface Dict1 { [k: string]: number }
21type Dict2 = { [k: string]: number };
22
23// ---- 只有 type 能做的 ----
24
25// 1. 联合类型
26type Status = "a" | "b";
27// interface Status2 = "a" | "b"; // 语法错误
28
29// 2. 元组类型
30type Pair = [number, number];
31// interface Pair2 extends [number, number] {} // 不能直接定义元组
32
33// 3. 条件类型
34type IsString<T> = T extends string ? true : false;
35
36// 4. 映射类型
37type Partial2<T> = { [K in keyof T]?: T[K] };
38
39// 5. 原始类型别名
40type ID = string | number;
41type Name = string;
42
43// 6. 模板字面量类型
44type Event = `on${string}`;
45
46// ---- 只有 interface 能做的 ----
47
48// 1. 声明合并
49interface Merged { a: number }
50interface Merged { b: string }
51const merged: Merged = { a: 1, b: "x" };
52
53// type 不能合并
54type TM = { a: number };
55// type TM = { b: string }; // error: Duplicate identifier
56
57// 2. extends 继承(比交叉更严格地检查)
58interface Base { a: number }
59interface Derived extends Base { b: string }
60
61// extends 可以继承多个
62interface Multi extends Base, Merged { c: boolean }
63
64// 3. 类可以实现多个 interface,但 implements 一个 type 联合不行
65interface A1 { a: number }
66interface B1 { b: number }
67class Impl implements A1, B1 {
68 a = 1;
69 b = 2;
70}
71
72// ---- extends 与 & 的一个关键差异 ----
73
74// interface extends:不能覆盖不兼容的属性
75// @ts-expect-error TS2430: Interface 'Bad' incorrectly extends interface 'Base'.
76interface Bad extends Base {
77 a: string;
78}
79
80// type 交叉:不报错,但结果是 never
81type BadType = Base & { a: string };
82// a 的类型变成 number & string = never
83const bad: BadType = { a: 1 as never };
84
85console.log(merged, new Impl(), bad);
1{ a: 1, b: 'x' } Impl { a: 1, b: 2 } { a: 1 }
| 能力 | interface | type |
|---|---|---|
| 描述对象结构 | ✓ | ✓ |
| 函数类型 | ✓ | ✓ |
| 泛型 | ✓ | ✓ |
| 索引签名 | ✓ | ✓ |
| 扩展 | extends | & 交叉 |
| 继承多个 | ✓ | ✓(交叉) |
| 声明合并 | ✓ | ✗ |
| 联合类型 | ✗ | ✓ |
| 元组类型 | ✗ | ✓ |
| 条件类型 | ✗ | ✓ |
| 映射类型 | ✗ | ✓ |
| 原始类型别名 | ✗ | ✓ |
| 模板字面量类型 | ✗ | ✓ |
只有 type 能做的
1type Status = "a" | "b"; // 联合
2type Pair = [number, number]; // 元组
3type IsString<T> = T extends string ? true : false; // 条件类型
4type Partial2<T> = { [K in keyof T]?: T[K] }; // 映射类型
5type ID = string | number; // 原始类型别名
6type Event = `on${string}`; // 模板字面量
interface 写不出这些,是语法层面的限制(interface X = "a" | "b" 是语法错误)。
只有 interface 能做的
声明合并是 interface 独有的:
1interface Merged { a: number }
2interface Merged { b: string } // 自动合并
3
4const merged: Merged = { a: 1, b: "x" };
type 会报 Duplicate identifier:
1type TM = { a: number };
2type TM = { b: string }; // error: Duplicate identifier 'TM'.
这是给第三方库补类型的基础(见 声明合并)。需要合并就必须用 interface。
extends 与 & 的关键差异
两者语义上都是"并且",但错误检查行为不同:
1interface Base { a: number }
2
3// interface extends:不兼容的覆盖会报错
4interface Bad extends Base {
5 a: string;
6}
7// error TS2430: Interface 'Bad' incorrectly extends interface 'Base'.
8// Types of property 'a' are incompatible.
9// Type 'string' is not assignable to type 'number'.
10
11// type 交叉:不报错,但属性类型变成 never
12type BadType = Base & { a: string };
13// a 的类型是 number & string,即 never
& 不会报错,只会静默产生 never这是 & 的一个陷阱。number & string 是 never,类型检查器不会提示你写错了,只会在后面赋值时以奇怪的方式失败。interface extends 会立刻报 TS2430,错误更早更清楚。
选择标准
用 interface:
- 描述对象结构(这是最常见的场景)
- 需要声明合并(给库补类型、扩展全局)
- 需要
extends的严格检查
用 type:
- 联合类型、元组、条件类型、映射类型
- 给原始类型起别名
- 组合多个类型(
A & B、A | B)
实用规则
默认用 interface,因为它可扩展、可合并、错误检查更严格。需要 type 独有的能力时再换 type。
反过来(默认 type,需要合并时换 interface)也行,但会导致团队里风格不统一。关键是定一个默认值并保持一致。
一个常见误区
interface 不是"只能描述对象",它也能描述函数和数组:
1interface Fn { (x: number): string }
2interface Arr extends Array<number> {}
反过来 type 也不是"只能描述联合",它同样能描述对象:
1type Obj = { a: number };
真正的区别只在上面那张能力表里。