业务开发中字典/枚举无处不在:订单状态、用户角色、性别、审核状态等。传统的 TS enum 有诸多痛点(反向映射、无法直接遍历、不是真正的运行时对象),而用 as const 定义字典数组或对象,再通过 typeof + keyof 推导 value 和 label 的联合类型,既能保留运行时数据,又能获得完整的类型安全。配合根据 value 查 label 的工具函数,是前端字典管理的最佳实践。
// 字典/枚举类型封装:as const 对象 + typeof 推导联合类型
// 运行时可遍历,编译期类型安全,替代传统 enum
// 1. 字典项基础类型
interface DictItem<V = string | number> {
value: V;
label: string;
color?: string;
disabled?: boolean;
}
// 2. 从 as const 字典数组推导 Value 联合类型
type DictValue<T extends readonly DictItem[]> = T[number]['value'];
// 3. 从 as const 字典数组推导 Label 联合类型
type DictLabel<T extends readonly DictItem[]> = T[number]['label'];
// 4. 示例:订单状态字典
const ORDER_STATUS = [
{ value: 0, label: '待支付', color: '#f59e0b' },
{ value: 1, label: '已支付', color: '#10b981' },
{ value: 2, label: '已发货', color: '#3b82f6' },
{ value: 3, label: '已完成', color: '#6366f1' },
{ value: 4, label: '已取消', color: '#ef4444' },
] as const;
// 5. 从字典推导出的类型
type OrderStatusValue = DictValue<typeof ORDER_STATUS>; // 0 | 1 | 2 | 3 | 4
type OrderStatusLabel = DictLabel<typeof ORDER_STATUS>; // '待支付' | '已支付' | ...
// 6. 对象形式的字典(按 value 映射)
const USER_ROLE = {
admin: { value: 'admin', label: '管理员', color: '#ef4444' },
editor: { value: 'editor', label: '编辑', color: '#f59e0b' },
viewer: { value: 'viewer', label: '访客', color: '#6b7280' },
} as const;
type UserRoleKey = keyof typeof USER_ROLE; // 'admin' | 'editor' | 'viewer'
type UserRoleValue = typeof USER_ROLE[keyof typeof USER_ROLE]['value']; // 'admin' | 'editor' | 'viewer'
// 7. 根据 value 查 label 的工具函数
function getLabelByValue<T extends readonly DictItem[]>(
dict: T,
value: DictValue<T>,
): string | undefined {
const item = dict.find(d => d.value === value);
return item?.label;
}
// 8. 泛型工具:获取字典项类型
type DictItemType<T extends readonly DictItem[]> = T[number];
// 使用示例
const status: OrderStatusValue = 1;
const label = getLabelByValue(ORDER_STATUS, status); // '已支付'
// 遍历字典(运行时可用)
const options = ORDER_STATUS.map(item => ({
value: item.value,
label: item.label,
}));
as const 收窄字面量:字典数组或对象加 as const 后,TS 会把每个字段收窄为字面量类型而非宽泛的 string/number,这是推导联合类型的前提。T[number]['value']:对数组类型用 [number] 索引得到元素类型,再用 ['value'] 取出 value 字段,自动形成所有 value 的联合类型。// value 传错会被 TS 拦截
// getLabelByValue(ORDER_STATUS, 99); // 错误:99 不在 0 | 1 | 2 | 3 | 4 中
// 变量受联合类型约束
let status: OrderStatusValue = 0;
status = 3; // OK
// status = 99; // 错误
// 字典项类型推导
type OrderStatusItem = DictItemType<typeof ORDER_STATUS>;
// => { readonly value: 0; readonly label: '待支付'; readonly color: '#f59e0b'; } | ...
// 业务接口中使用字典值类型
interface Order {
id: number;
status: OrderStatusValue; // 只能是 0-4
role: UserRoleValue; // 只能是 admin/editor/viewer
}
// 生成 select 选项
function toSelectOptions<T extends readonly DictItem[]>(dict: T) {
return dict.map(item => ({
value: item.value,
label: item.label,
disabled: item.disabled,
}));
}
const opts = toSelectOptions(ORDER_STATUS);