在前端开发中,我们经常遇到枚举值需要多处复用的情况,比如:
- 用作接口查询参数(如账户类型、交易状态等)
- 渲染下拉选择框
- 表格或详情页中显示对应文本
如果每次都手动书写 if
、switch
或维护多份结构,很容易导致维护成本高、逻辑重复且容易出错。本文介绍一种结构化的编码风格,通过统一的数据源构建 枚举 + 列表 + Map
,提高代码的可维护性、可读性和可扩展性。
代码示例:交易流水类型
下面是一个典型的枚举结构示例:
ini
/**
* 交易类型枚举
*/
export enum TRANSACTION_TYPE {
/** 全部 */
ALL = '',
/** 支出 */
EXPENSE = 0,
/** 收入 */
INCOME = 1,
}
该枚举常用于接口查询参数、路由跳转等。但单独的枚举值并不能满足组件渲染和文案展示的需求,因此我们通过统一的数据源进行扩展。
构建统一的数据源
我们使用一个数组来作为"单一数据源":
css
const TRANSACTION_TYPE_SOURCE: { value: TRANSACTION_TYPE; label: string }[] = [ { value: TRANSACTION_TYPE.INCOME, label: '收入' }, { value: TRANSACTION_TYPE.EXPENSE, label: '支出' },];
优势:
- 集中定义
- 结构扁平,易于维护
- 可直接用于组件绑定
构建下拉选项和映射 Map
在实际项目中,这个枚举值可能会用于:
- 下拉框渲染(需要
label + value
结构) - 表格/详情中的文案展示(需要通过 Map 映射)
于是我们进一步封装:
ini
export const TRANSACTION_TYPE_LIST = [
{ value: TRANSACTION_TYPE.ALL, label: '全部' },
...TRANSACTION_TYPE_SOURCE,
];
export const TRANSACTION_TYPE_MAP = new Map(
TRANSACTION_TYPE_SOURCE.map(item => [item.value, item.label])
);
这样:
TRANSACTION_TYPE_LIST
可用于<a-select>
、<el-select>
等组件TRANSACTION_TYPE_MAP
可用于表格、标签、详情等位置的文字渲染
可选的安全获取函数
为了更安全地获取 label,推荐加一个辅助函数:
typescript
export function getTransactionTypeLabel(type: TRANSACTION_TYPE | ''): string {
if (type === TRANSACTION_TYPE.ALL) return '全部';
return TRANSACTION_TYPE_MAP.get(type) || '--';
}
Bonus:提炼通用工具函数
如果你项目中存在多个类似的枚举结构,重复手写 list
和 map
结构不仅效率低,而且容易遗漏或写错。可以封装一个通用工具函数 createEnumHelper
,标准化整个流程。
通用工具函数封装
建议新建一个工具文件:
arduino
src/utils/enum-helper.ts
内容如下:
ts
/**
* 通用枚举工具函数:用于创建枚举数据的统一管理接口
* @template T - 枚举值的类型
* @param items - 枚举项的原始数据,包含 value 和 label
* @param options - 可选参数:`includeAll` 用于是否包含 "全部" 选项,`allLabel` 用于定义 "全部" 选项的标签
* @returns 返回一个对象,包括 `list`(下拉列表)、`map`(映射表)、`getLabel`(获取label的函数)
*/
export function createEnumHelper<T>(
items: { value: T; label: string }[],
options?: { includeAll?: boolean; allLabel?: string },
) {
// 解构出 options,设置默认值
const { includeAll = false, allLabel = '全部' } = options || {};
// 如果需要包含"全部"选项,将其放在列表最前面
const list = includeAll
? [{ value: '' as any as T, label: allLabel }, ...items] // 添加 '全部' 选项
: [...items]; // 如果不需要"全部",直接返回原数据
// 创建枚举值与标签的映射关系
const map = new Map<T, string>(items.map(item => [item.value, item.label]));
/**
* 根据 value 获取对应的 label
* @param value - 枚举值,支持传入空字符串(对应 "全部")
* @returns 返回对应的 label,若无匹配返回 '--'
*/
function getLabel(value: T): string {
// 如果是空字符串且包含 "全部",返回 "全部" 标签
if (value === '' && includeAll) return allLabel;
// 如果 Map 中有该值,则返回对应的 label;否则返回默认值 '--'
return map.get(value) || '--';
}
// 返回包含 list、map 和 getLabel 的对象
return {
list, // 用于下拉框的列表数据
map, // 用于映射的 Map
getLabel, // 获取 label 的函数
};
}
使用方式示例
在任意业务模块中使用它非常简单:
ini
// enums/transaction-type.ts
import { createEnumHelper } from '@/utils/enum-helper';
export enum TRANSACTION_TYPE {
ALL = '',
EXPENSE = 0,
INCOME = 1,
}
const TRANSACTION_TYPE_SOURCE = [
{ value: TRANSACTION_TYPE.INCOME, label: '收入' },
{ value: TRANSACTION_TYPE.EXPENSE, label: '支出' },
];
// 调用工具函数生成枚举列表、Map 和 label 获取函数
export const {
list: TRANSACTION_TYPE_LIST,
map: TRANSACTION_TYPE_MAP,
getLabel: getTransactionTypeLabel,
} = createEnumHelper(TRANSACTION_TYPE_SOURCE, { includeAll: true });
使用场景示例
1. 用于下拉框:
ini
<a-select
v-model:value="form.transactionType"
:options="TRANSACTION_TYPE_LIST"
placeholder="请选择类型"
/>
2. 用于表格渲染:
javascript
{
title: '交易类型',
dataIndex: 'transactionType',
customRender: ({ text }) => getTransactionTypeLabel(text),
}
总结
这种编码风格具备以下优点:
优点 | 描述 |
---|---|
统一性 | 所有枚举值统一来源 |
可扩展 | 新增类型只需维护一处 |
类型安全 | 枚举提供强类型保障 |
解耦 | 不依赖 UI 或具体实现,纯数据驱动 |
对于中大型系统,建议将这套方式纳入团队开发规范,配合统一的 enum-helper.ts
工具文件进行集中管理,能显著提升代码质量和开发效率。
如果你还希望扩展功能(如多语言支持、枚举权限控制等),这套结构也能非常轻松地适配与升级。