三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

TypeScript 灵魂拷问:type 和 interface 到底怎么选?

TypeScript 灵魂拷问:type 和 interface 到底怎么选?

很多同学在写 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" };

你会发现,在这个层面interfacetype几乎可以无缝互换。它们都可以:

  • 约束对象、变量
  • 用于函数参数和返回值

这才是最迷惑人的地方——如果只是定义个简单的对象,用谁都行。真正的区别,藏在“变化”里。

二、第一个分歧:接口可以声明合并,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封闭的。记住这个特性,在需要扩展第三方类型的时候,你会感谢它。

三、第二个分歧:继承方式不一样

面向对象里,“继承”是复用结构的重要手段。两者都能实现,但语法和组合能力不同。

interfaceextends关键字,符合经典的 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> ); };

在这里,UserUserCardProps都是明确的“协议”——它们描述了组件和外界交互的形状。用interface来定义,可以享受声明合并的便利(比如未来要给User加字段),也能清晰地表达“我是接口”。

反观一些临时的工具类型、数据转换类型,直接用type快速组合:

typescript

type UserBrief = Pick<User, 'name' | 'avatarUrl'>; type Status = 'loading' | 'success' | 'error';

七、总结

让我们把核心区别浓缩成一张表:

特性interfacetype
描述对象结构
声明合并(同名自动合并)❌(同名报错)
继承方式extends&交叉类型
定义联合/元组等非对象类型
函数类型定义调用签名箭头语法,更简洁
适用场景契约、组件 Props、可扩展的类型工具类型、联合类型、临时别名

最重要的建议其实只有一句话:

定义对象形状,用interface,除非你需要联合类型或复杂映射——那时再用type

这样选,既符合 TypeScript 的 OOP 基因,也能在大型项目中保持类型的一致性和可扩展性。

好了,下次面试官再问你这个问题,相信你已经胸有成竹。记住,技术选型没有绝对的对错,关键是让你的代码意图更清晰,让协作的人少一些困惑

← 返回列表