Appearance
TypeScript 类型系统基础:interface、type、enum 与泛型
很多人学 TypeScript 时,最容易卡住的不是“完全不会写”,而是:
- 知道这些关键词见过很多次
- 但一到真实项目里,不知道该优先用哪个
- 能写出语法,却说不清它到底在解决什么问题
这篇文章专门把最常见、最容易混的几组关键词收口到一条主线里:
interfacetypeenum- 泛型
🌟 把这层分工记住:interface 更偏对象契约,type 更偏类型表达和组合,enum 更偏有限常量集合,泛型更偏“同一套逻辑如何在不同类型上复用且不丢类型信息”。`
1. 为什么这些关键词容易混
TypeScript 看起来只是“多写点类型”,但真正落到工程里时,你很快就会遇到这些问题:
- 一个对象结构该用
interface还是type - 一组有限状态值该用
enum还是联合字面量类型 - 一个通用函数如果想保留输入输出之间的类型关系,该怎么写
如果这些边界没分清,最后常见会变成两种极端:
- 什么都用
type - 什么都用
interface
更稳的做法不是背“唯一标准答案”,而是先分清每个关键词最擅长表达什么。
2. interface 到底在解决什么问题
interface 可以看成:用来描述对象结构和契约的一种方式。
它最适合回答的问题是:
- 这个对象应该有哪些字段
- 这些字段各自是什么类型
- 某个类或某组数据需要满足什么结构约束
例如:
ts
interface User {
id: number
name: string
email?: string
}这段代码表达的是:
User至少要有idname是必填email可以没有
2.1 interface 更适合哪些场景
工程里更常见的场景包括:
- 接口返回对象
- 组件
props - 类需要实现的契约
- 配置对象的结构定义
例如:
ts
interface Payable {
pay(amount: number): void
}
class OrderService implements Payable {
pay(amount: number) {
console.log(`pay ${amount}`)
}
}这里 interface 的重点不是“多一个语法糖”,而是:把能力契约说清楚。
2.2 interface 的一个典型特点
interface 很适合不断扩展同一个对象契约。
例如:
ts
interface BaseUser {
id: number
name: string
}
interface AdminUser extends BaseUser {
role: 'admin'
}它在“描述对象形状”这条线里会显得很自然。
3. type 到底在解决什么问题
type 可以看成:给一个类型表达式起名字。
这句话里最关键的是“类型表达式”。
因为 type 不只会描述对象,它还能描述:
- 联合类型
- 交叉类型
- 元组
- 字面量类型
- 条件类型
- 工具类型组合后的结果
例如:
ts
type RequestStatus = 'idle' | 'loading' | 'success' | 'error'
type Point = [number, number]
type UserWithToken = User & {
token: string
}这些写法如果只靠 interface,表达起来就不自然了。
3.1 type 更适合哪些场景
它更适合:
- 想给联合类型命名
- 想组合多个类型
- 想描述元组
- 想写更灵活的类型别名
把这层边界拉开看:
interface更偏“对象契约”type更偏“类型表达能力”
3.2 interface 和 type 怎么选
不要把它们讲成绝对对立。
更稳的选择方式通常是:
| 场景 | 更自然的选择 |
|---|---|
描述对象结构、组件 props、接口契约 | interface |
| 联合类型、交叉类型、元组、字面量类型别名 | type |
| 单纯对象结构,团队也统一偏向一种写法 | 二者都可以,但要保持风格一致 |
🌟 要注意的是,interface 和 type 都能描述对象,但 type 的表达范围更大;而对象契约场景下,interface 往往更直观。
4. enum 到底在解决什么问题
enum 可以看成:给一组有限常量值起一组有名字的成员。
例如:
ts
enum OrderStatus {
Pending = 'PENDING',
Paid = 'PAID',
Cancelled = 'CANCELLED',
}它更像在表达:
- 订单状态只有这些值
- 每个值都应该有稳定的语义化名字
4.1 enum 的价值是什么
它的价值通常包括:
- 常量语义更集中
- 代码补全更直接
- 比散落的字符串更不容易写错
例如:
ts
function isPaid(status: OrderStatus) {
return status === OrderStatus.Paid
}4.2 为什么很多项目又不总是优先用 enum
这是很容易被忽略的边界。
在很多前端项目里,一组有限状态值也经常写成:
ts
type OrderStatus = 'PENDING' | 'PAID' | 'CANCELLED'这是因为联合字面量类型也能表达“有限取值范围”,而且通常更轻。
所以更稳的理解应该是:
enum适合需要一组具名常量成员的场景- 如果只是想约束取值范围,联合字面量类型也很常见
工程上很多团队会优先这样选:
- 如果重点是“表达有限取值”,优先联合字面量类型
- 如果重点是“集中管理一组具名常量”,
enum依然很自然
4.3 不要把 enum 理解成什么
不要把它理解成“所有状态值都必须用它”。
enum 只是 TypeScript 提供的一种有限常量组织方式,不是唯一方案。
5. 泛型到底在解决什么问题
泛型真正要解决的,不是“写复杂语法”,而是:
同一套逻辑需要复用在不同类型上,但复用之后仍然要保留输入和输出之间的类型关系。
例如:
ts
function first<T>(list: T[]): T | undefined {
return list[0]
}这里的重点不是 T 这个字母,而是:
- 传入
string[]时,返回值就应该是string | undefined - 传入
number[]时,返回值就应该是number | undefined
如果没有泛型,常见会退化成两种糟糕写法:
- 每种类型都复制一份函数
- 直接写成
any
5.1 泛型最常见出现在哪
工程里最常见的地方包括:
- 函数
- 接口
- 类型别名
- 工具类型
- 组件和 Hook 封装
例如:
ts
interface ApiResponse<T> {
code: number
message: string
data: T
}
type UserResponse = ApiResponse<{
id: number
name: string
}>这里 ApiResponse<T> 的价值就在于:外层结构复用,但 data 的具体类型仍然保留下来。
5.2 泛型约束在做什么
泛型不是完全不受限制的。
很多时候你会希望它至少满足某个条件,例如必须有 id 字段:
ts
function getId<T extends { id: number }>(value: T) {
return value.id
}这里的 extends 不是在做继承实现,而是在做:
给泛型参数增加一个最小结构约束。
5.3 泛型什么时候不要滥用
如果一段逻辑只会服务一种明确类型,就不要为了“看起来高级”强行抽成泛型。
🌟 泛型应该服务复用和保留类型关系,而不是把简单问题写复杂。
6. 这几个关键词之间的关系怎么串起来
可以用一张表把它们压缩起来:
| 关键词 | 更偏解决什么 |
|---|---|
interface | 描述对象契约和结构 |
type | 给类型表达式命名和做类型组合 |
enum | 组织有限常量集合 |
| 泛型 | 复用逻辑时保留类型信息 |
如果把它们放进一个真实开发场景里,可以这样理解:
- 用
interface定义接口返回对象结构 - 用
type定义状态值联合、组合类型或工具类型结果 - 用
enum或联合字面量类型表达有限状态 - 用泛型把公共函数、响应结构、组件能力做成可复用模板
7. 一个更贴近工程的综合示例
下面这段代码把这几个关键词放在同一个场景里看:
ts
interface User {
id: number
name: string
}
type RequestStatus = 'idle' | 'loading' | 'success' | 'error'
enum UserRole {
Admin = 'ADMIN',
Member = 'MEMBER',
}
interface PageResult<T> {
list: T[]
total: number
}
function getFirstItem<T>(result: PageResult<T>): T | undefined {
return result.list[0]
}这里可以直接看到:
User用interface,因为重点是描述对象结构RequestStatus用type,因为重点是表达有限联合类型UserRole用enum,因为想把角色常量集中命名PageResult<T>和getFirstItem<T>用泛型,因为想复用结构和逻辑,同时保留具体类型
8. 一份更实用的选择清单
当你写 TypeScript 时,可以问自己:
- 我现在是在描述对象结构吗
- 我是在给联合类型、元组或组合类型起名字吗
- 我是在组织一组有限常量吗
- 我是在做逻辑复用,同时还想保留类型关系吗
对应地可以这样选:
- 对象契约优先想
interface - 类型表达和组合优先想
type - 有限常量集合考虑
enum或联合字面量类型 - 复用逻辑且不想丢类型信息时考虑泛型
9. 小结
如果把这篇文章压缩成一句话,可以记住:
interface 更偏对象契约,type 更偏类型表达,enum 更偏有限常量集合,泛型更偏可复用逻辑里的类型保留。真正重要的,不是把这些关键词全背下来,而是知道当前这段代码到底想表达“结构”“范围”“常量”还是“复用”。`