在长期维护中后台业务的过程中,我发现‘硬编码’的状态逻辑是导致代码劣化的主因。有了 local-dict 这个工具,不仅是为了简化模板中的条件判断,更是为了利用 TypeScript 的类型推导能力,为前端业务字典建立一套‘定义即类型,配置即驱动’的标准流,从而实现业务语义与工程逻辑的深度解耦。
在前端业务开发中,你是否经常被这种代码折磨:
- 满屏魔术数字:
if (status === 1)—— 这个1到底是谁? - 反查 Label 靠手写:
list.find(i => i.val === v)?.label—— 逻辑重复,满屏undefined风险。 - 重构如排雷:后端改了个状态值,你得全项目搜索字符串,生怕漏改一处导致事故。
1. 为什么不直接用 enum?
很多同学问:用原生的 enum 或者 const Object 不行吗?看下对比就知道了:
| 对比项 | 原生 enum / Object | local-dict |
|---|---|---|
| Label 支持 | 需手动维护额外 Map | 定义即绑定,一行获取 |
| 扩展属性 | 难以挂载颜色、图标等 UI 属性 | 原生支持,且具备完整的 IDE 类型提示 |
| 类型推导 | 弱,需手动写断言 | 强类型约束,支持自动推导联合类型 |
| 运行时能力 | 转换 List 或反查逻辑繁琐 | 高度集成,提供标准化的 API 调用 |
2. 适用场景
- 业务状态字典:如订单状态、支付类型等。
- 枚举 + UI 映射:状态与颜色、图标、国际化 Key 绑定。
- 表单选项:快速生成 Select、Radio 的数据源。
- 中后台系统:对代码健壮性和可维护性要求极高的项目。
- 对 TS 类型质量有追求:不想在项目中到处写
as any。
3. 快速开始
1. 安装依赖
npm install local-dict
2. 创建字典
import { createLocalDict } from 'local-dict';
const STATUS = createLocalDict({
SUCCESS: { label: '成功', value: 1, type: 'success' },
FAIL: { label: '失败', value: 2, type: 'danger' },
UNKNOWN: { label: '未知', value: -1, type: 'info' }
});
4. 用法
4.1 基础调用
// 获取原始 Value
STATUS.SUCCESS.getValue(); // 1
// 获取展示 Label
STATUS.SUCCESS.getLabel(); // '成功'
// 获取条目在原始数组中的索引
STATUS.SUCCESS.getIndex(); // 0
// 将接口字段转换为中文
STATUS.getLabel4Value(status) // 具体label
// 获取标准格式,适合 Select 或 Radio 组件。
STATUS.getList() // [{ label: '成功', value: 1, type: 'green' }, ...]
4.2 包含判断与类型收窄
// 如果status被命中,则status的类型会缩小为 STATUS.SUCCESS.getValue() | STATUS.FAIL.getValue()
if (STATUS.includes(status, [STATUS.SUCCESS.getValue(), STATUS.FAIL.getValue()])) {
// TODO
} else {}
4.3 属性扩展:自定义业务字段
// 很多时候需要自定义字段,比如ui渲染为tag,每个状态对应不同的字体、背景颜色
// 获取 status 的的颜色
STATUS.getItemMap().get(status)?.type // success | danger | info | undefined
5. 实操(diff)
- 根据 row.status 渲染标签, 不再写一堆 v-if 判断
<el-table>
<el-table-column label="状态">
<template #default="{ row }">
- <el-tag v-if="row.status === 1" type="success">成功</el-tag>
- <el-tag v-else-if="row.status === 2" type="danger">失败</el-tag>
- <el-tag v-else type="info">未知</el-tag>
+ <el-tag :type="STATUS.getItemMap().get(row.status)?.type">
+ {{ STATUS.getLabel4Value(row.status) }}
+ </el-tag>
</template>
</el-table-column>
</el-table>
- 获取列表渲染,适合 Select 或 Radio 组件的绑定数据
<el-select v-model="formData.status">
<el-option
- v-for="item in options"
+ v-for="item in STATUS.getList()"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
- 操作按钮显隐
如果是多个状态都可以显示,不要使用 [STATUS.SUCCESS.getValue()].includes(row.status) 判断,需要使用 STATUS.includes(row.status, [STATUS.SUCCESS.getValue()]), 因为 includes 类型收窄原因, 会导致 ts 报错
// 单个状态判断
<div>
- <el-button v-if="row.status == 2" type="success">完成</el-button>
+ <el-button v-if="row.status == STATUS.SUCCESS.getValue()" type="success">完成</el-button>
</div>
// 多个状态判断
<div>
- <el-button v-if="[1, 2].includes(row.status)" type="success">完成</el-button>
+ <el-button v-if="STATUS.includes(row.status, [STATUS.SUCCESS.getValue(), STATUS.FAIL.getValue()])" type="success">完成</el-button>
</div>
6. 扩展
如果需要在type/interface申明字段类型,直接通过字面量/string | number ... 的方式是不好维护的,local-dict 这个版本还没有实现,可以通过下面DictValues来推断value的联合类型。
1.0.1 已提供
DictValues
import type { DictInstance, DictValueOf } from 'local-dict'
type DictValues<S> = S extends DictInstance<infer T> ? DictValueOf<T> : never;
//用法:
interface Vo {
status: DictValues<typeof STATUS>; // 1 | 2 | -1
}