很多同学在写 TS 时,总会纠结:定义对象结构,到底用type还是interface?面试也总被问到它们的区别。其实,答案不是一句“都能用”那么简单,而是藏在真实的开发场景里。
今天,我们就用实战代码 + 踩坑经验,把这个问题彻底讲透,让你以后再也不用纠结。
一、它们的共同起点:都能定义对象结构
先看一段最熟悉的代码:
interface User { name: string; age: number; avatarUrl: string; } type UserType = { name: string; age: number; avatarUrl: string; }; const u1: User = { name: '张三', age: 18, avatarUrl: "https://example.com/avatar.jpg" }; const u2: UserType = { name: '李四', age: 20, avatarUrl: "https://example.com/avatar.jpg" };你会发现,在这个层面interface和type几乎可以无缝互换。它们都可以:
- 约束对象、变量
- 用于函数参数和返回值
这才是最迷惑人的地方——如果只是定义个简单的对象,用谁都行。真正的区别,藏在“变化”里。
二、第一个分歧:接口可以声明合并,type 不行
这是面试几乎必问的点。接口有一个非常“动态”的特性:同名接口会自动合并。
interface Animal { name: string; } // 其他地方,甚至另一个文件里 interface Animal { age: number; } const dog: Animal = { name: '三寸钉', age: 2 }; // 完全合法这种“声明合并”在做第三方库的类型扩充时特别有用。比如你想给 Vue 的组件实例上加一个自定义属性,就可以利用这个特性。
而type是静态的别名,绝对不能重名:
type AnimalType = { name: string }; // ❌ 报错:标识符“AnimalType”重复 type AnimalType = { age: number };说白了,interface是开放的,type是封闭的。记住这个特性,在需要扩展第三方类型的时候,你会感谢它。
三、第二个分歧:继承方式不一样
面向对象里,“继承”是复用结构的重要手段。两者都能实现,但语法和组合能力不同。
interface用extends关键字,符合经典的 OOP 直觉:
interface Person { name: string; } interface Employee extends Person { job: string; } const e1: Employee = { name: 'cjz', job: '字节Agent开发工程师' };type则用交叉类型(&)来实现类似继承:
type PersonType = { name: string }; type EmployeeType = PersonType & { job: string }; const e2: EmployeeType = { name: 'lg', job: '大厂' };从结果看似乎一样,但当类型复杂时,&的能力更强——它能一次性组合多个类型,包括联合类型、函数类型等,而extends只能继承静态已知的结构。
四、最核心的差异:type 能描述“非对象”类型
这是type的主场。很多情况下,我们要定义的根本不是一个对象:
// 联合类型 type ID = string | number; // 元组类型 type Point = [number, number]; // 函数签名(更简洁) type AddType = (a: number, b: number) => number;你没办法用interface直接定义string | number这种联合类型,因为接口只能表达对象形状。你可能会想尝试:
// ❌ 语法错误 interface ID = string | number;所以,当你需要一个灵活的、跨类型的别名时,请直接选type。
五、函数类型:表面相似,实则各有优势
两者都可以定义函数类型:
// interface 写法:像一个对象里藏了个调用签名 interface AddFn { (a: number, b: number): number; } // type 写法:更像个箭头函数,直观 type AddType = (a: number, b: number) => number; const add1: AddFn = (x, y) => x + y;初看只是语法糖的区别,但type在描述函数重载和组合函数类型时,配合条件类型、泛型会更加灵活。而interface定义函数的写法,其实更贴合 JS 里“函数也是对象”的原始设计——你可以在签名旁边再挂载一些属性。不过日常开发中,绝大多数场景用type定义函数更简洁。
一个朴素的经验法则:单看函数,用type;涉及可调用对象的完整描述,考虑interface。
六、实战选型:别争了,看场景
技巧:interface 是给“协议”用的,type 是给“数据”用的。
在实际的 React/Vue 开发中,最能体现这个原则。看看下面这个 React 组件:
// 定义一个实体的接口(将来可能扩展、可能让别的模块实现) interface User { name: string; age: number; avatarUrl: string; } // 组件的 Props 也是一个“契约” interface UserCardProps { user: User; onEdit: (id: number) => void; } const UserCard: React.FC<UserCardProps> = ({ user, onEdit }) => { return ( <div> <h1>User Card</h1> </div> ); };在这里,User和UserCardProps都是明确的“协议”——它们描述了组件和外界交互的形状。用interface来定义,可以享受声明合并的便利(比如未来要给User加字段),也能清晰地表达“我是接口”。
反观一些临时的工具类型、数据转换类型,直接用type快速组合:
typescript
type UserBrief = Pick<User, 'name' | 'avatarUrl'>; type Status = 'loading' | 'success' | 'error';七、总结
让我们把核心区别浓缩成一张表:
| 特性 | interface | type |
|---|---|---|
| 描述对象结构 | ✅ | ✅ |
| 声明合并(同名自动合并) | ✅ | ❌(同名报错) |
| 继承方式 | extends | &交叉类型 |
| 定义联合/元组等非对象类型 | ❌ | ✅ |
| 函数类型定义 | 调用签名 | 箭头语法,更简洁 |
| 适用场景 | 契约、组件 Props、可扩展的类型 | 工具类型、联合类型、临时别名 |
最重要的建议其实只有一句话:
定义对象形状,用
interface,除非你需要联合类型或复杂映射——那时再用type。
这样选,既符合 TypeScript 的 OOP 基因,也能在大型项目中保持类型的一致性和可扩展性。
好了,下次面试官再问你这个问题,相信你已经胸有成竹。记住,技术选型没有绝对的对错,关键是让你的代码意图更清晰,让协作的人少一些困惑。