文章

interface 与 type 的终极对比

interface 与 type 的终极对比

一句话概括

interfacetype 都能描述对象形状,但 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 用 typetype 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 进行授权