【Typescript】04-数组元组枚举与字面量类型

数组、元组、枚举与字面量类型

这一篇要讨论四个看似接近、实际上经常被混用的概念:数组、元组、枚举和字面量类型。它们都和"值的集合"有关,但解决的是完全不同的问题。很多项目里的类型建模之所以写得模糊,往往不是不会写,而是没有分清这些工具各自表达的到底是什么。

从工程角度看,这一篇非常重要。因为你接下来会越来越频繁地遇到这些场景:

  • 一组同类数据要怎么表示
  • 一组固定顺序的数据要怎么表示
  • 一组有限状态要怎么限制
  • 一组固定选项是用 enum 还是别的方式

如果这些基础分不清,后面的联合类型和判别联合也会变得别扭。

数组表达的是"同类的一批数据"

最常见的数组写法有两种:

ts 复制代码
const scores: number[] = [90, 88, 95];
const tags: Array<string> = ["ts", "js", "node"];

这两种语法在 TypeScript 里是等价的。重点不在形式,而在含义:数组表示元素类型一致,但数量不固定。

也就是说,当你写 number[] 时,实际在说:

  • 这个集合里可以有很多值
  • 每个值都应该是 number
  • 具体有几个元素并不重要

这非常适合:

  • 成绩列表
  • 标签列表
  • 用户列表
  • 接口返回的分页数据项

混合类型数组通常意味着结构不够清晰

ts 复制代码
const values = [1, "hello", true];

这时 TypeScript 会推断成 (number | string | boolean)[]。从语法上说这是合法的,但从建模角度看,通常说明这个结构还没想清楚。

因为一个数组如果什么都能塞,后续使用时就会非常难受。你要不断判断当前元素到底是什么类型,代码既啰嗦又脆弱。

更常见也更合理的做法往往是:

  • 要么拆成对象
  • 要么拆成多个更清晰的数组
  • 要么确认这其实是元组,不是数组

元组表达的是"每个位置都有意义"

ts 复制代码
const point: [number, number] = [100, 200];
const userInfo: [number, string, boolean] = [1, "Alice", true];

元组和数组最本质的区别,不是写法,而是建模意图:

  • 数组强调"同类批量"
  • 元组强调"固定位置、固定长度、每个位置含义不同"

例如一个二维坐标很适合元组,因为第一个位置一定是 x,第二个位置一定是 y。一个返回 [data, error] 的工具函数,也可能适合元组,因为这两个位置的语义非常明确。

什么时候不该用元组

元组虽然紧凑,但不是默认优选。只要一个结构里的字段开始具备明确业务语义,通常对象会比元组更可读。

比如这两个写法:

ts 复制代码
const userInfo: [number, string, boolean] = [1, "Alice", true];
ts 复制代码
const user = {
  id: 1,
  name: "Alice",
  active: true
};

第二种通常更容易维护,因为你不需要记住"第三个位置是不是表示启用状态"。所以一个很实用的原则是:

  • 强调顺序和位置时,用元组
  • 强调字段语义时,用对象

字面量类型表达的是"有限且精确的值"

ts 复制代码
let direction: "left" | "right";
direction = "left";

这里 direction 不再是任意字符串,而只能是 "left""right"。这正是字面量类型的价值:它让类型从宽泛的范围,收紧成明确的合法状态集合。

同样的思路可以用在大量业务状态中:

ts 复制代码
type OrderStatus = "pending" | "paid" | "cancelled";
type ThemeMode = "light" | "dark";
type RequestState = "idle" | "loading" | "success" | "error";

这种写法比直接写 string 强得多,因为它会让非法状态在编码阶段就被阻止。

为什么字面量类型在工程里这么重要

因为真实业务里,大量字段并不是"任意字符串",而是"只能从有限集合中取值"。例如:

  • 订单状态
  • 用户角色
  • 组件尺寸
  • 请求状态
  • 语言代码

如果你把这些都写成 string,等于把本该清晰的状态空间又放宽了。后续所有逻辑判断都建立在一个模糊前提上,代码会越来越难收敛。

enum 是传统方案,但不一定是现代默认方案

TypeScript 提供了 enum

ts 复制代码
enum Role {
  Admin,
  User,
  Guest
}

或者字符串枚举:

ts 复制代码
enum Status {
  Pending = "pending",
  Done = "done"
}

enum 的优点是语义明确,历史也很长,所以你在很多老项目和教程里都会看到它。

但在现代 TypeScript 项目里,越来越多团队更喜欢使用"对象常量 + 字面量联合"的方式:

ts 复制代码
const ROLE = {
  ADMIN: "admin",
  USER: "user",
  GUEST: "guest"
} as const;

type Role = typeof ROLE[keyof typeof ROLE];

这种写法第一次看可能会觉得啰嗦,但它有几个工程优势:

  • 运行时就是普通 JavaScript 对象
  • 类型和值天然保持一致
  • 更方便和现代工具链协作
  • 避免某些 enum 编译产物上的心智负担

那我到底该用 enum 还是字面量联合

比较务实的建议是:

  • 你要读懂 enum,因为历史代码里很多
  • 你在新项目里,可以优先考虑字面量联合或 as const 对象

这不是说 enum 不能用,而是说你要知道它不是唯一方案。很多团队选择不用 enum,并不是因为它错误,而是因为更偏向贴近 JavaScript 本体的写法。

一个很典型的建模对比

假设你要表示订单状态。下面三种写法,清晰度完全不同。

最弱的写法

ts 复制代码
let status: string;

这几乎没有约束,任何字符串都能进来。

更好的写法

ts 复制代码
type OrderStatus = "pending" | "paid" | "cancelled";
let status: OrderStatus;

这时状态空间已经被限制在真实业务允许的范围内。

更接近工程实践的写法

ts 复制代码
const ORDER_STATUS = {
  PENDING: "pending",
  PAID: "paid",
  CANCELLED: "cancelled"
} as const;

type OrderStatus = typeof ORDER_STATUS[keyof typeof ORDER_STATUS];

这样你同时拥有:

  • 运行时可复用常量
  • 编译期精确类型

这就是为什么现代 TypeScript 项目里,这类写法越来越普遍。

常见误区

误区一:所有多值结构都用数组

很多人看到"一组值"就先写数组,但真实需求可能是固定结构、固定顺序、有限状态,并不一定适合数组。

误区二:能写 string 就不想写字面量联合

这会让本该被限制的状态重新变宽。你短期省了几个字符,长期会多出更多判断和 bug。

误区三:把元组当成节省对象定义的捷径

元组不是为了省字段名,而是为了表达位置语义。如果一个结构需要靠注释才能知道每个位置代表什么,那它大概率更适合对象。

本文小结

数组强调的是"同类的一批数据",元组强调的是"固定位置的结构化数据",字面量类型强调的是"有限状态集合",而 enum 是 TypeScript 提供的一种传统组织方式。把这些工具分清楚,你的建模能力会立刻提升一个层级。

真正好的类型设计,往往不是写得多,而是选得准。你能准确判断"这里需要批量、这里需要位置、这里需要状态限制",后面的代码自然会更稳。

练习

  1. 用元组表示一个 RGB 颜色值,并思考它和对象写法各自的优缺点。
  2. 用字面量联合定义订单状态:pendingpaidcancelledrefunded
  3. 把一个 enum 改写成对象常量加字面量联合的形式,并比较两者的使用体验。

后记

2026年5月21日于上海。

相关推荐
Qres8219 分钟前
docker & WSL & Ubuntu安装记录
ubuntu·docker·容器·wsl
阿猫的故乡12 分钟前
Vue + Axios 从入门到封装:拦截器、错误处理、请求取消、接口管理全搞定
前端·javascript·vue.js
wuxia21181 小时前
在5种环境中编写点击元素改变内容和颜色的JavaScript程序
javascript·微信小程序·vue·jquery·react
用户484526255821 小时前
Bun 入门:Bun.serve 零依赖启动 HTTP 服务
typescript
铁皮饭盒2 小时前
Bun + SQLite 10个实用技巧
前端·javascript·后端
想吃火锅10053 小时前
【leetcode】20.有效的括号js
linux·javascript·leetcode
aaaa954726653 小时前
终端与IDE形态Vibe Coding实测:主流AI编程工具迁移与迭代对比
javascript·react.js·ecmascript
晓得迷路了3 小时前
栗子前端技术周刊第 133 期 - Angular v22、React 编译器 Rust 版、pnpm 11.5...
前端·javascript·css
云浪3 小时前
别再让用户干等了:用 Express + SSE 实现《红楼梦》AI 问答实时输出
javascript·后端·node.js
晓13133 小时前
【Cocos Creator 3.x】篇——第五章 项目实战优化技术
前端·javascript·游戏引擎