【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日于上海。

相关推荐
神奇小汤圆10 小时前
自己用 ai 写了个链接 mysql 数据库的 mcp 工具
javascript
kgduu10 小时前
ethers.js学习笔记
javascript·笔记·学习
狼丶宇先森10 小时前
vue-sign-canvas v2 重构复盘:从 Vue 2 签名板到 Vue 3 + TypeScript 组件库
前端·vue.js·重构·typescript·开源软件·canvas
爱吃龙利鱼10 小时前
MobaXterm连接ubuntu26.04无法在vim界面粘贴问题解决方法(粘贴会提示进入进入可视模式VISUAL))
linux·ubuntu·编辑器·vim
迁旭10 小时前
Claude Code 项目 /init 命令详解
前端·javascript·chrome·机器学习·语言模型·gpt-3
marsh020610 小时前
53 openclaw插件市场:开发与发布自己的插件
开发语言·前端·javascript
用户9385156350710 小时前
全栈小项目实战:从零搭建用户列表
javascript
前端繁华如梦10 小时前
three.js从盒子到链条的程序化三维实现
前端·javascript
Oo92010 小时前
做一个用户列表页面,把模块化与语义化搞懂
javascript·全栈