✅Vue代码整洁:从硬编码到数据字典映射

198 阅读3分钟

在长期维护中后台业务的过程中,我发现‘硬编码’的状态逻辑是导致代码劣化的主因。有了 local-dict 这个工具,不仅是为了简化模板中的条件判断,更是为了利用 TypeScript 的类型推导能力,为前端业务字典建立一套‘定义即类型,配置即驱动’的标准流,从而实现业务语义与工程逻辑的深度解耦。

在前端业务开发中,你是否经常被这种代码折磨:

  • 满屏魔术数字if (status === 1) —— 这个 1 到底是谁?
  • 反查 Label 靠手写list.find(i => i.val === v)?.label —— 逻辑重复,满屏 undefined 风险。
  • 重构如排雷:后端改了个状态值,你得全项目搜索字符串,生怕漏改一处导致事故。

1. 为什么不直接用 enum?

很多同学问:用原生的 enum 或者 const Object 不行吗?看下对比就知道了:

对比项原生 enum / Objectlocal-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' }
});

q3xpt-07zx3.gif

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)

  1. 根据 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>
  1. 获取列表渲染,适合 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>
  1. 操作按钮显隐

如果是多个状态都可以显示,不要使用 [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
}

image.png

7. 相关链接

📦 Npm

🐙 Github