在 TypeScript 的学习和面试中,type(类型别名)和 interface(接口)的区别几乎是必考题。很多初学者觉得它们长得像、用法也像,甚至觉得完全可以混用。但实际上,它们在 TypeScript 的类型系统中扮演着截然不同的角色。
今天这篇文章,我们不仅要把它们的核心区别讲透,还会结合实际的开发场景(比如 React 组件开发、面向对象编程),带你彻底搞懂到底该怎么选。
一、 共同点:它们都是"对象的形状描述器"
首先,我们要明确一个共识:在描述对象结构 时,type 和 interface 几乎是完全等价的。它们都可以用来给对象、函数参数、返回值做类型约束。
ini
// 使用 interface 定义用户结构
interface User {
name: string;
age: number;
avatarUrl: string;
}
// 使用 type 定义完全相同的用户结构
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' };
深度解析:
TypeScript 采用的是"结构化类型系统"(Structural Type System)。简单来说,TS 不关心你是用 interface 还是 type 定义的,它只关心你的对象"长什么样"。只要属性的名称和类型对得上,TS 就认为它们是兼容的。
二、 核心区别一:继承机制的不同
这是面试中最常考的点之一。虽然它们都能实现类型的复用和扩展,但语法和底层逻辑完全不同。
1. interface 使用 extends 关键字
interface 的继承非常符合传统面向对象编程(OOP)的直觉,它使用 extends 关键字,并且支持同时继承多个接口(多继承)。
php
interface Person {
name: string;
}
// 继承 Person,并扩展了 job 属性
interface Employee extends Person {
job: string;
}
const e1: Employee = { name: '季季红', job: '字节Agent开发工程师' };
2. type 使用 & (交叉类型)
type 没有 extends 关键字,它通过 &(交叉类型运算符)将多个类型合并为一个新类型。
ini
type PersonType = { name: string };
// 通过 & 合并类型
type EmployeeType = PersonType & { job: string };
const e2: EmployeeType = { name: '李四', job: '大厂的苗子' };
深度解析与避坑:
虽然结果看起来一样,但 extends 和 & 在处理同名属性时表现不同。
- interface 继承:如果子接口和父接口有同名属性,TS 会严格检查类型兼容性,如果不兼容会直接报错。
- type 交叉 (&) :如果两个类型有同名属性,该属性的类型会被合并为"从不"类型(never),导致这个属性根本无法赋值。
三、 核心区别二:声明合并 vs 重复声明报错
这个特性决定了它们在大型项目和第三方库扩展中的不同地位。
1. interface 支持"声明合并"(Declaration Merging)
如果你定义了多个同名的 interface,TypeScript 不会报错,而是会自动将它们合并成一个接口。
kotlin
interface Animal {
name: string;
}
// 同名接口自动合并
interface Animal {
age: number;
}
// 现在的 Animal 同时拥有 name 和 age
const dog: Animal = {
name: '三寸钉',
age: 2,
}
实战场景:
这个特性在扩展全局对象或第三方库时非常有用。比如你想给原生的 Window 对象添加自定义属性,只需要写一个 interface Window { myCustomProp: string } 即可,而不需要修改原生代码。
2. type 是封闭的,禁止重复声明
type 一旦定义,就是封闭的。如果你尝试定义两个同名的 type,编译器会直接抛出"标识符重复"的错误。
ini
type AnimalType = { name: string };
// 报错:Duplicate identifier 'AnimalType'
type AnimalType = { age: number };
四、 核心区别三:能否表示"非对象类型"
这是 type 最大的杀手锏。interface 只能定义对象的结构,而 type 可以定义任何类型。
1. 联合类型(Union Types)
ini
// type 可以轻松定义联合类型
type ID = string | number;
// interface 无法做到
// interface ID = string | number; (语法错误)
2. 元组类型(Tuples)
typescript
// 定义固定长度和类型的数组
type Point = [number, number];
3. 基础类型别名与映射类型
ini
// 基础类型别名
type Status = 'pending' | 'success' | 'error';
// 映射类型(高级类型体操必备)
type ReadonlyPoint = {
readonly [K in keyof Point]: Point[K];
};
深度解析:
如果你需要定义的是"非对象"的结构,或者需要用到 TS 的高级类型工具(如 Partial, Pick, Omit 等),必须使用 type 。interface 在这些场景下无能为力。
五、 核心区别四:函数类型的表达差异
两者都可以描述函数,但在语法风格上有所不同。
1. interface 的"调用签名"
interface 通过在接口内部定义调用签名来表达函数类型:
typescript
interface AddFn {
(a: number, b: number): number;
}
const add1: AddFn = (x, y) => x + y;
2. type 的"函数类型别名"
type 的写法更加简洁直观,类似箭头函数的语法:
typescript
type AddType = (a: number, b: number) => number;
const add2: AddType = (x, y) => x + y;
实战建议:
在日常开发中,定义简单的函数类型时,type 的写法更简洁。但如果你需要定义一个"既是函数,又有自定义属性"的复杂对象(比如 jQuery 的 $ 选择器),interface 的调用签名结合属性定义会更方便。
六、 核心区别五:OOP 场景与 React 开发规范
1. 类的实现(implements)
在传统的面向对象编程中,class 只能 implements(实现)一个 interface,而不能实现一个 type。
typescript
interface User {
name: string;
age: number;
}
// 正确
class Admin implements User {
name: string = 'admin';
age: number = 30;
}
// 错误:Class 'Manager' incorrectly implements type 'UserType'.
// Did you mean to extend 'UserType' and use it as a value instead?
type UserType = { name: string; age: number };
class Manager implements UserType {
name: string = 'manager';
age: number = 40;
}
2. React 组件的 Props 定义
在前端 React 开发中,我们通常用它们来约束父子组件的数据接口。
typescript
interface UserCardProps {
user: User;
onEdit: (id: number) => void;
}
const UserCard: React.FC<UserCardProps> = ({ user, onEdit }) => {
return (
<div>
<h1>User Card</h1>
</div>
);
};
深度解析:
虽然 React 官方文档早期推荐用 interface 定义 Props(因为支持声明合并,方便第三方库扩展),但在现代 React + TS 开发中,使用 type 定义 Props 已经成为主流 。因为 Props 通常是封闭的,不需要被合并,且 type 在定义联合 Props 或工具类型时更灵活。
七、 终极总结:到底该怎么选?
经过上面的深度剖析,我们可以总结出一套清晰的选型规范:
-
优先使用
interface的场景:- 定义对象的形状(Shape),尤其是需要被
class实现时。 - 需要利用"声明合并"特性扩展全局对象或第三方库类型时。
- 团队有严格的 OOP 规范,强调契约精神时。
- 定义对象的形状(Shape),尤其是需要被
-
必须使用
type的场景:- 需要定义联合类型(
|)、元组、枚举值映射时。 - 需要使用高级类型工具(如
Pick,Omit, 条件类型T extends U ? X : Y)时。 - 定义简单的函数类型、回调函数时。
- 需要定义联合类型(
-
都可以的场景:
- 定义普通的 React Props、API 响应数据结构等。此时建议团队内部保持统一,不要在一个文件里混用。
面试加分话术:
"interface 更像是一种面向对象的契约,它是开放的,支持合并和继承,适合定义稳定的数据结构;而 type 是类型系统的瑞士军刀,它是封闭的,但支持联合、交叉、映射等所有高级类型运算。在实际工程中,我通常用 interface 定义核心业务模型,用 type 处理复杂的类型推导和工具函数。"