4. 使用枚举管理订单状态流转
为什么订单状态适合用枚举
订单状态在业务代码中会反复出现:创建订单、支付、发货、确认收货、取消。如果直接写 "pending"、"paid" 这类字符串,拼写错误、大小写不一致、重构改名都会在运行时才暴露。字符串枚举把状态收敛到一处定义,让编译器参与检查。
本案例定义 5 个状态:Pending(待支付)、Paid(已支付)、Shipped(已发货)、Completed(已完成)、Cancelled(已取消)。状态流转规则为:待支付 → 已支付或已取消;已支付 → 已发货;已发货 → 已完成。其余流转一律拒绝。
定义字符串枚举
使用字符串枚举而非数字枚举,序列化到 JSON 或日志时保留可读值,调试时不必反查数字含义。
enum OrderStatus {
Pending = "PENDING",
Paid = "PAID",
Shipped = "SHIPPED",
Completed = "COMPLETED",
Cancelled = "CANCELLED",
}
interface Order {
id: string;
status: OrderStatus;
}
用枚举成员做分支判断
流转函数接收当前订单和目标状态,先查合法迁移表,再决定是否更新。分支判断全部使用 OrderStatus.Xxx,不出现裸字符串。
const transitions: Record<OrderStatus, OrderStatus[]> = {
[OrderStatus.Pending]: [OrderStatus.Paid, OrderStatus.Cancelled],
[OrderStatus.Paid]: [OrderStatus.Shipped],
[OrderStatus.Shipped]: [OrderStatus.Completed],
[OrderStatus.Completed]: [],
[OrderStatus.Cancelled]: [],
};
function transition(order: Order, next: OrderStatus): Order {
const allowed = transitions[order.status];
if (!allowed.includes(next)) {
throw new Error(
`非法流转: ${order.status} -> ${next}`
);
}
return { ...order, status: next };
}
把
transitions 声明为 Record<OrderStatus, OrderStatus[]>,如果漏写某个状态,TypeScript 会直接报错,迁移表始终完整。验证流转与拒绝
const order: Order = { id: "A1001", status: OrderStatus.Pending };
const paid = transition(order, OrderStatus.Paid);
console.log(paid.status); // "PAID"
const shipped = transition(paid, OrderStatus.Shipped);
console.log(shipped.status); // "SHIPPED"
// 非法流转:已发货不能直接取消
transition(shipped, OrderStatus.Cancelled);
// 抛出 Error: 非法流转: SHIPPED -> CANCELLED
常见错误与规避
| 错误写法 | 问题 | 正确做法 |
|---|---|---|
order.status === "paid" | 大小写与枚举值不一致,永远为 false | order.status === OrderStatus.Paid |
函数参数写 string | 调用方可传入任意字符串,失去编译检查 | 参数类型写 OrderStatus |
迁移表用 Record<string, string[]> | 漏状态不报错,键名拼错也不报错 | 键和值都用 OrderStatus |
枚举成员是运行时对象,
Object.values(OrderStatus) 可遍历全部状态值;但反向映射只对数字枚举存在,字符串枚举没有 OrderStatus["PENDING"] 这种反查。