TypeScript2025.11.06 · 14 min read
Interface vs Type
interface 定义是什么,type 描述什么关系。
L
Leo
2025.11.06 · Updated 2026.08.25
2 views
TypeScript: interface vs type 终极指南
面向全栈工程实践(Node.js + React),提供可落地的选择标准与团队约定。
一、一句话速记
✅ 对象结构 / API 契约 →
interface✅ 组合、变换、联合、工具类型 →type
更口语化:
- "这是个东西" →
interface - "这是种关系 / 变形" →
type
二、决策表(速查)
| 场景 | 推荐 | 原因 |
|---|---|---|
| 定义对象 / DTO / VO / Props | ✅ interface | 语义清晰、可扩展 |
| 定义 API 请求 / 响应结构 | ✅ interface | 贴近契约、易维护 |
| 定义函数类型 | ✅ type | 简洁、可读性好 |
| 联合类型 / 交叉类型 | ✅ type | interface 不支持 |
| 字面量联合(替代 enum) | ✅ type | 唯一选择 |
| 可辨识联合(Discriminated Union) | ✅ type | 事实标准 |
| 扩展第三方类型(声明合并) | ✅ interface | type 做不到 |
| 工具类型 / 条件类型 / 映射类型 | ✅ type | interface 做不到 |
| React Props(简单对象) | ✅ interface | 社区主流 |
| React Props(复杂联合) | ✅ type | 必须 |
三、本质差异
3.1 interface:面向"对象结构"
- 语义:"实现这个契约"
- 设计目标:OOP、继承、扩展
- 特点:
- ✅ 声明合并(Declaration Merging)
- ✅
extends继承 - ✅ 更适合人类阅读
interface User {
id: number;
name: string;
}
读起来像:"User 是一个拥有 id 和 name 的对象"
3.2 type:面向"类型关系"
- 语义:"这是某种类型"
- 设计目标:类型别名、组合、变换
- 特点:
- ✅ 联合
| - ✅ 交叉
& - ✅ 条件类型
T extends U ? A : B - ✅ 映射类型
- ✅ 联合
type ID = number | string;
type UserOrError = User | Error;
读起来像:"ID 可以是 number 或 string"
四、坚决用 interface 的场景
4.1 定义对象的结构契约
// ✅ 强烈推荐
interface CreateUserDto {
email: string;
password: string;
}
interface UserResponse {
id: number;
email: string;
}
好处:
- 一眼知道这是一个对象
- 后期加字段成本低
- 非常适合 DTO / VO / Entity
4.2 扩展第三方库(声明合并)
// ✅ 扩展 Express 的 Request
declare module 'express-serve-static-core' {
interface Request {
userId: number;
}
}
只有
interface能做这件事,type不行。
4.3 React Props(99% 情况)
interface ButtonProps {
type?: 'primary' | 'default';
loading?: boolean;
onClick?: () => void;
}
原因:
- Props 本质是对象
- 组件消费方更容易理解
- 社区 & ESLint 生态默认偏好
五、坚决用 type 的场景
5.1 联合类型 / 可辨识联合
// ✅ 必须用 type
type ApiResult<T> =
| { ok: true; data: T }
| { ok: false; error: string };
interface无法直接表达"或"关系。
5.2 字面量联合(替代 enum)
// ✅ 推荐
type Status = 'idle' | 'loading' | 'success' | 'error';
比 enum 更好:
- 无 JS 运行时成本
- Tree-shaking 友好
- 类型推导更强
5.3 函数类型
// ✅ 更清晰
type Handler = (req: Request, res: Response) => void;
// ❌ 可读性差
interface Handler {
(req: Request, res: Response): void;
}
5.4 工具类型 / 条件类型 / 映射类型
type ReadonlyUser = Readonly<User>;
type Nullable<T> = T | null;
type Maybe<T> = T | null | undefined;
type KeysOfType<T, U> = {
[K in keyof T]: T[K] extends U ? K : never;
}[keyof T];
interface完全做不到这些。
六、联合类型与 interface 的关系
6.1 interface 成员可以是联合类型
interface User {
id: number;
role: 'admin' | 'user' | 'guest'; // ✅ 成员是联合类型
status: 'active' | 'banned' | null;
}
6.2 多个 interface 可以组成联合类型
interface Admin {
type: 'admin';
permissions: string[];
}
interface User {
type: 'user';
email: string;
}
// ✅ 可辨识联合
type Account = Admin | User;
使用时必须收窄类型:
function handle(account: Account) {
if (account.type === 'admin') {
console.log(account.permissions); // ✅ Admin
} else {
console.log(account.email); // ✅ User
}
}
6.3 ❌ 不能这样写
// ❌ 非法语法
interface A | B {
x: number;
}
七、常见误区
❌ 误区一:混用导致团队混乱
interface User {
id: number;
}
// ❌ 风格不统一
type UserWithEmail = User & { email: string };
✅ 更推荐统一写法:
// 方案 A:全 interface
interface User {
id: number;
}
interface UserWithEmail extends User {
email: string;
}
// 方案 B:全 type
type User = {
id: number;
};
type UserWithEmail = User & {
email: string;
};
原则:同一"层级"的类型,尽量统一
interface或type。
❌ 误区二:用可选属性模拟联合
// ❌ 不推荐
interface User {
id: number;
email?: string;
phone?: string;
}
问题:
- TS 无法强制 "email / phone 至少存在一个"
- 运行时还得自己校验
✅ 正确做法:
type Contact =
| { kind: 'email'; email: string }
| { kind: 'phone'; phone: string };
八、生产项目团队约定(可直接抄)
8.1 前端 / React
// Props / State / Ref —— 用 interface
interface ButtonProps {}
interface ListState {}
interface InputRef {}
// 事件回调 —— 用 type
type ButtonClickHandler = (id: number) => void;
// 状态集合 —— 用 type
type RequestStatus = 'idle' | 'loading' | 'success' | 'error';
8.2 Node / API
// 请求 / 响应 —— 用 interface
interface CreateUserReq {
email: string;
password: string;
}
interface CreateUserRes {
id: number;
email: string;
}
// 业务结果 —— 用 type
type ServiceResult<T> =
| { ok: true; data: T }
| { ok: false; error: string };
8.3 公共工具类型 —— 用 type
type Maybe<T> = T | null | undefined;
type Await<T> = T extends Promise<infer U> ? U : T;
type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> };
九、联合类型速览(附录)
什么是联合类型?
一个值可以是多种类型中的一种:A | B
let value: string | number;
value = 'hello'; // ✅
value = 123; // ✅
value = true; // ❌
核心规则
- 赋值时:只要符合其中任意一个类型就行
- 使用时:只能访问所有类型共有的成员
- 必须通过类型收窄才能安全使用特定成员
function printId(id: string | number) {
if (typeof id === 'string') {
console.log(id.toUpperCase()); // ✅
} else {
console.log(id.toFixed(2)); // ✅
}
}
常见场景
// 字面量联合
type Status = 'success' | 'error' | 'loading';
// API 响应封装
type Result<T> =
| { ok: true; data: T }
| { ok: false; error: string };
// 可空值
type UserOrNull = User | null;
十、一句话终极总结
interface= 定义"是什么"type= 描述"是什么关系"
- 定义对象形状 →
interface - 组合 / 变换 / 联合 →
type - 同一项目内保持风格一致,比选哪个更重要
文档版本:1.0 | 适用 TypeScript 5.x | 更新日期:2026-08-07
TagsTypeScript
L
Leo
BloggerIndependent developer / Blogger and the maintainer of the original blog “大道至简”. Migrating years of posts and shiyu from WordPress to Next.js.
Reader comments
COMMENTS · 0Leave a comment