文章

interface 与 type 的终极对比深度解析

interface 支持声明合并(给第三方库打补丁),type 能表达联合、交叉、元组、映射类型(interface 做不到)。 公开库类型用 interface 留扩展点,带 variant 的 Props 用 type 写联合,多数对象两者皆可。

interface 与 type 的终极对比深度解析

一句话概括

interface 能声明合并(给第三方库打补丁),type 能表达联合/交叉/元组/映射类型(interface 做不到)。这不是喜好选择题——库的公开类型用 interface 留扩展点,组件 Props 带 variant 用 type 写联合类型,大多数对象定义两者都行。

核心知识点

1. 对象定义——大部分场景等价

1
2
3
4
5
interface User { name: string; age: number }
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
// 同名 interface 自动合并
interface User { name: string }
interface User { age: number }
// 最终类型:{ name: string; age: number }

// 🔑 真正价值:给第三方库扩展类型
declare interface Window {
  __INITIAL_STATE__: AppState; // 不覆盖 Window 原有属性,只是追加
}

// type 重复定义 → 直接报错
// type User = { age: number }  // ❌ Duplicate identifier

注意: 非函数同名成员类型冲突会报错;同名函数成员自动按声明顺序合并为重载。

3. type 的独占领域——interface 做不到的

1
2
3
4
5
6
7
8
9
10
11
12
13
// 联合类型 —— type 的王牌
type Status = 'pending' | 'success' | 'error';

// 元组
type Point = [number, number];

// 映射类型 / 条件类型 —— 只能用 type
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type ReturnOf<T> = T extends (...args: any[]) => infer R ? R : never;

// 从值提取类型
const config = { host: 'localhost', port: 3000 };
type Config = typeof config;

4. 扩展方式的隐藏差异——extends vs & 的致命区别

1
2
3
4
5
6
7
8
9
10
11
12
13
interface A { a: string }
interface B extends A { b: number }  // extends

type T = A & { b: number }           // 交叉 &

// 可以互相配合
interface C extends A { c: Date }    // interface extends type ✅
type D = A & { d: Date }             // type & interface ✅

// ⚠️ 致命差异:同名属性冲突
interface X { a: number }
// interface Y extends X { a: string } // ❌ 编译直接报错 —— 立刻发现!
type Z = X & { a: string }            // ✅ 编译通过 —— 但 a 的类型是 never!

extends 遇到冲突立刻报错帮你发现;& 把冲突属性静默合并成 never——编译过了但那个属性没法用。 此外,interface 是命名类型(可缓存复用),type 的交叉类型需要递归展平——大规模类型检查时 interface 性能更优。

5. 选型决策表

场景推荐原因
库的公开 API 类型interface留声明合并扩展点 + 性能更好
组件 Props(含 variant 联合)type需要 'a' \| 'b' 联合类型
映射/条件/工具类型typeinterface 做不到
给第三方库补丁interface声明合并
日常对象定义团队统一两者都行

其实你每天都在用

  • 给 window 补丁:declare interface Window { appConfig: Config } —— 只有 interface 能不破坏原有类型
  • 组件 variant Props:type BtnProps = { variant: 'primary' \| 'danger' } & HTMLAttributes —— 联合 + 交叉一把梭
  • 开源库 .d.ts 全是 interface:Vue、React 的类型定义大量用 interface,给你留扩展点且性能更优
  • API 响应泛型:interface ApiRes<T> { code: number; data: T } —— 对象形状用 interface 语义更贴切
  • 后端枚举约束:type Role = 'admin' \| 'editor' \| 'viewer' —— 字面量联合只能用 type

常见误解(FAQ)

  • ❌ 误区:「interface 是 type 的子集」 恰恰相反——两者是大部分重叠 + 各自独占:type 独占联合/映射/条件类型;interface 独占声明合并。不是谁包含谁,是交集 + 互补。

  • ❌ 误区:「type 不适合大型项目因为不能 extends」 type 用 & 组合,表达能力甚至更强。但 extends 遇到冲突报错更早、interface 检查性能更好——这才是选 extends 的真实理由,而不是”type 不能 extends”。

  • ❌ 误区:「extends 和 & 遇到冲突都报错」 不一样。extends 直接报红帮你发现问题。& 把冲突属性合并成 never——编译通过了但运行时废了。很多人踩这个坑后坚定站 extends。

  • ❌ 误区:「React 官方用 interface,我也用」 React 类型定义源码中两者都大量使用。TS 官方文档的态度是:说清楚用哪个都行,无硬性标准。团队统一 > 个人偏好。

一句话总结

interface 给公开 API 留扩展,type 给复杂类型做表达。不是谁更好,而是谁更适合你要解决的问题——成熟项目 .d.ts 里两者混用才是常态。

本文由作者按照 CC BY 4.0 进行授权