Type vs Interface:读完这篇就没有面试官能难倒你了
引言
在 TypeScript 中,type 和 interface 是日常开发里最高频出现的两个关键词。它们都能描述对象的结构,但底层机制有本质区别。面试官最爱问的 TS 问题,十有八九就是这个。本文将从一个 React 函数组件 的实际场景出发,结合笔记和代码,把它们的异同一次性讲透。
一、共同点:都能描述"对象的形状"
先看一段最基础的代码:
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" };
共同之处在于 :interface 和 type 都可以:
- 描述一个对象的结构------包含哪些属性,每个属性是什么类型
- 用于函数参数的类型注解
- 用于函数返回值类型
- 给变量、对象做类型约束
它们在 对象形状描述 这个层面,语法不同但能力等价。
二、区别一:继承机制完全不同
这是一个底层差异------两者实现"继承/组合"的方式截然不同。
ini
interface Person {
name: string;
}
// interface 用 extends 继承,像 Java 的面向对象
interface Employee extends Person {
job: string;
}
// type 用交叉类型 & 组合
type PersonType = { name: string };
type EmployeeType = PersonType & { job: string };
const e1: Employee = { name: "张三", job: "字节开发工程师" };
const e2: EmployeeType = { name: "李四", job: "大厂的苗子" };
关键理解:
| interface | type | |
|---|---|---|
| 语法 | extends(继承) |
&(交叉类型) |
| 语义 | "我是一个 Person,并且多了 job" | "把 PersonType 的所有属性 和 { job } 合并" |
| 背后 | 面向接口编程的 名义类型 思路 | 类型层面的 结构运算 |
extends 背后是 TypeScript 对传统 OOP 的致敬------它显式声明了 Employee 是 Person 的子类型。而 & 是一种类型代数运算,它不关心"谁是谁的子类型",只是把两个类型的属性集合做并集。
三、区别二:声明合并------interface 的"按揭声明"能力
这是 interface 最独特的能力,也是面试的高频考点:
typescript
interface Animal {
name: string;
}
// 同名 interface 会自动合并!
interface Animal {
age: number;
}
const dog: Animal = { name: "三寸钉子", age: 2 }; // ✅ 两个属性都必须有
// type 呢?
type AnimalType = { name: string };
type AnimalType = { age: number }; // ❌ 报错!标识符重复
为什么会这样?
TypeScript 编译器对 interface 做了特殊处理------遇到同名声明时,会自动合并成员 而非视为重复定义。这叫 Declaration Merging(声明合并) 。
这个设计的价值在于:
typescript
场景:你引用了一个第三方库的类型定义,想在不修改源码的情况下补充属性。
第三方 .d.ts:
interface Window { appName: string }
你的代码:
interface Window { appVersion: number } // 合并,不报错!
使用时:
window.appName // ✅
window.appVersion // ✅
type 不行,因为 type 本质上是一个类型别名 ------对已有类型的引用。同一个作用域内,同一个名字不能指向两个不同的东西,这和 const 不可重复声明是同一个道理。
四、区别三:type 能表示的东西 interface 做不到
这是 type 的不可替代之处------它能描述非对象类型:
typescript
// 联合类型 ------ interface 做不到
type ID = string | number;
// 元组类型 ------ interface 做不到
type Point = [number, number];
// interface 试一下?
interface ID {} // 空的,完全无法表达 "string 或 number" 这个语义
typescript
interface 能表达的 type 能表达的
┌─────────────────┐ ┌───────────────────────┐
│ 对象结构 │ │ 对象结构 │
│ { a: string } │ │ { a: string } │
│ │ │ │
└─────────────────┘ │ 联合类型 string|number │
│ 元组 [number,number]│
│ 字面量 'a'|'b' │
│ 映射类型 等等... │
└───────────────────────┘
理解本质 :type 是类型别名 ,它可以指向任意类型表达式------对象类型、联合类型、元组、原始类型、字面量类型......而 interface 的设计目的就是定义对象的契约,它天然只能描述对象形状。
五、区别四:函数类型------都能写,type 更简洁
typescript
// interface 定义函数类型 ------ 需要写完整的方法签名
interface AddFN {
(a: number, b: number): number;
}
const add1: AddFN = (x, y) => x + y;
// type 定义函数类型 ------ 像箭头函数一样自然
type AddType = (a: number, b: number) => number;
两者都能表达函数类型,但 type 的语法更接近日常写函数的方式,理解成本更低。interface 则需要写带调用签名的对象------它依然是把函数当作"可调用的对象"来描述,这是 OOP 语系下的思维惯性。
六、实战:React 函数组件里为什么首选 interface
回到实际工程。在 React + TypeScript 项目中,组件 Props 几乎都是用 interface 定义的:
typescript
// 接口 ------ 传统 OOP 核心概念:抽象
// JS 本身是原型式的、函数式一等公民
// TS 为大型企业级开发提供强类型,思路偏 Java 传统 OOP
// class extends / implements interface
// 面向接口编程 ------ 父子组件之间的数据契约
interface User {
name: string;
age: number;
avatarUrl: string;
}
interface UserCardProps {
user: User;
onEdit: (id: number) => void;
}
const UserCard: React.FC<UserCardProps> = ({ user, onEdit }) => {
return (
<div>
<img src={user.avatarUrl} alt={user.name} />
<span>{user.name} · {user.age}岁</span>
<button onClick={() => onEdit(user.id)}>编辑</button>
</div>
);
};
为什么函数组件的 Props 要选 interface?
-
语义对齐 :Props 天然是"契约"------父组件说"我提供这些数据",子组件说"我需要这些数据"。
interface的extends继承和 OOP 的接口编程一脉相承,表达的正是这个"契约"概念。 -
可扩展性 :用
interface定义的 Props 可以被extends扩展出更具体的变体,这和多态组件模式天然匹配:kotlininterface BaseCardProps { user: User } interface AdminCardProps extends BaseCardProps { permissions: string[] } -
声明合并 :第三方组件库的 Props 类型,你可以通过
interface的同名合并能力无缝扩展,而不用改库代码。 -
官方与社区惯例 :React 类型定义本身(
React.FC、React.ComponentProps等)大量使用interface,社区主流项目(Ant Design、Material UI)的组件 Props 也清一色interface。跟社区走,可读性最高。
七、一张图总结
go
选择指南
│
┌────────────────┼────────────────┐
│ │ │
需要描述对象形状? 需要联合/元组? 不确定?
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│interface│ │ type │ │interface│
│ │ │ │ │ (默认) │
│ React │ │ 工具类型│ │ 可扩展性│
│ Props │ │ 灵活组合│ │ 更友好 │
│ 契约设计│ │ 类型体操│ │ │
└─────────┘ └─────────┘ └─────────┘
一句话记忆:
interface定义"契约"(对象能做什么),type创造"别名"(这个东西叫什么)。写组件 Props 用interface,写工具类型用type。
两者既互补又互有交叉,理解它们的底层差异,你才能在代码评审时说清楚"这里为什么选这个"------而不只是"我看别人都这么写"。