TypeScript 状态映射实战:从枚举到多语言的最佳实践

在前后端分离的开发模式中,后端接口常通过数字或字符串形式的枚举值(如 01"draft")表示业务状态、类型等信息。而前端不仅需要将这些值转换为用户友好的文本(如中文“草稿”、英文“Draft”),还需考虑多语言切换、状态合法性校验等问题。本文将深入探讨 ​5 种常见的状态映射方案,从最基础的枚举反向映射到面向企业的 Class 封装,分析其适用场景、类型安全性和扩展能力。

一. ​直接根据值渲染

<template>
  <div class="content">
    <!-- 直接根据 valueFromBackend 显示状态 -->
    <span v-if="valueFromBackend === StatusMap.DRAFT">未确认</span>
    <span v-else-if="valueFromBackend === StatusMap.PUBLISHED">已发布</span>
    <span v-else-if="valueFromBackend === StatusMap.DELETED">已删除</span>
    <span v-else>未知状态</span>
  </div>
</template>

<script setup lang="ts">
//用原生js定义
const StatusMap = {
  DRAFT: 0,
  PUBLISHED: 1,
  DELETED: 2
};
//使用ts定义
export enum Status {
  DRAFT = 0,
  PUBLISHED = 1,
  DELETED= 2
}
const valueFromBackend = 0; // 假设后端返回 0
</script>

优点

  • 简单快捷:无需额外定义映射对象,直接利用 TypeScript 枚举的反向映射特性。
  • 代码量少:适合快速原型开发或简单状态判断。

缺点

  • 强耦合枚举键名:模板中硬编码枚举键名(如 'Unconfirmed'),键名修改后需全局替换。
  • 无法处理非法值:若 row.type 为非法值(如 3),Status[3] 返回 undefined,但无错误提示。
  • 不支持多语言:文本直接写在模板中,难以扩展国际化。
  • 类型不安全:反向映射的返回值类型为 string | undefined,需手动处理。

适用场景

  • 临时调试或小型项目,无需长期维护。
  • 状态值稳定且无需国际化。

二. ​数组遍历写法

<template>
  <ul>
    <li v-for="status in statusList" :key="status.value">
      <template v-if="valueFromBackend === status.value">
        {{ status.label }}
      </template>
    </li>
  </ul>
</template>

<script setup>
const statusList = [
  { value: 0, label: '未确认' },
  { value: 1, label: '已确认' }
];
const valueFromBackend = 0;
</script>

优点

  • 结构清晰:状态值与文本集中管理,便于扩展新状态。
  • 动态渲染:适合需要遍历所有状态并高亮当前值的场景(如状态筛选菜单)。

缺点

  • 性能开销:每次渲染需遍历整个数组,状态较多时可能影响性能。
  • 逻辑重复:状态判断逻辑分散在模板中,可能产生冗余代码。
  • 类型松散:未使用 TypeScript 约束,可能因拼写错误导致运行时问题。

适用场景

  • 需要动态生成状态列表的界面(如筛选器、选项卡)。
  • 状态数量较少且变化频繁。

三. ​枚举 + 映射对象写法

<template>
  <div class="content">
    <span>{{ StatusChinese[valueFromBackend as Status] }}</span>
  </div>
</template>

<script setup lang="ts">
enum Status {
  Unconfirmed = 0,
  Confirmed = 1,
}

const StatusChinese = {
  [Status.Unconfirmed]: '未确认',
  [Status.Confirmed]: '已确认',
} as const;

const StatusEnglish = {
  [Status.Unconfirmed]: 'unconfirmed',
  [Status.Confirmed]: 'Confirmed',
} as const;
// 获取中文
console.log(StatusChinese[backendValue]); // 输出 "草稿"

// 获取英文
console.log(StatusEnglish[backendValue]); // 输出 "Draft"

const valueFromBackend = 0; // 假设后端返回 0
</script>

优点

  • 类型安全:通过 as Status 和 as const 确保类型正确,编译时检查非法值。
  • 解耦键名与文本:修改枚举键名不影响显示逻辑,只需更新映射对象。
  • 扩展性强:轻松支持多语言(替换映射对象即可)。
  • 集中维护:状态文本统一管理,避免硬编码。

缺点

  • 代码量略多:需定义枚举和映射对象,小型项目可能显得冗余。
  • 学习成本:需熟悉 TypeScript 高级特性(如 as const 和类型守卫)。

适用场景

  • 中大型项目或长期维护的代码库。
  • 需要国际化、主题切换等扩展功能。

四.Class 封装

class OrderStatus {
  // 定义枚举值
  static readonly Draft = 0;
  static readonly Published = 1;
  static readonly Deleted = 2;

  // 私有映射表
  private static zhMap = {
    [OrderStatus.Draft]: "草稿",
    [OrderStatus.Published]: "已发布",
    [OrderStatus.Deleted]: "已删除",
  } as const;

  // 获取中文
  static getChinese(value: number): string {
    return this.zhMap[value as keyof typeof this.zhMap] || "未知状态";
  }

  // 校验合法性
  static isValid(value: number): boolean {
    return Object.values(OrderStatus).includes(value);
  }
}

// 使用示例
console.log(OrderStatus.getChinese(0)); // "草稿"

优点

  • 逻辑高度集中:状态值、映射关系、校验方法全部封装在类中。
  • 强封装性:私有映射表 (zhMap) 避免外部误修改。
  • 易于扩展:可添加更多方法(如多语言、日志记录)。
  • 类型安全:静态方法配合 keyof 实现安全访问。

缺点

  • 代码冗余:简单场景下显得过于繁琐。
  • 过度设计:小型项目可能不需要完整的面向对象结构。
  • 初始化成本:需实例化或静态调用,略失灵活性。

适用场景

  • 复杂状态逻辑(如状态机、流程审批)。
  • 需要集中管理校验、转换、日志等附加功能。
  • 团队熟悉面向对象设计模式。

五.联合类型 + 对象映射

// 定义联合类型
type StatusValue = 0 | 1 | 2;

// 定义映射对象
const StatusMap = {
  0: { en: "Draft", zh: "草稿" },
  1: { en: "Published", zh: "已发布" },
  2: { en: "Deleted", zh: "已删除" },
} as const;

// 类型安全访问函数
const getStatusText = (value: StatusValue) => {
  return StatusMap[value] || { en: "Unknown", zh: "未知状态" };
};

// 使用示例
console.log(getStatusText(0).zh); // "草稿"

优点

  • 极简轻量:无需定义枚举或类,直接通过对象和类型约束。
  • 灵活直观:对象字面量清晰表达键值关系。
  • 类型精确:联合类型 StatusValue 严格限制取值范围。
  • 无额外抽象:适合快速开发或简单状态管理。

缺点

  • 维护成本高:新增状态需修改联合类型和映射对象。
  • 无法动态扩展:联合类型需静态定义所有可能值。
  • 复用性差:逻辑分散,不适合跨组件复用。

适用场景

  • 状态数量固定且极少变化的场景。
  • 轻量级工具函数或临时模块。
  • 开发者偏好函数式编程风格。

六.总结

1. ​简单场景

  • 推荐方案:初级写法(反向映射)或数组遍历写法
  • 理由:快速实现,代码量少,适合临时需求或状态极少变化的场景。

2. ​企业级项目

  • 推荐方案:枚举 + 映射对象 或 Class 封装
  • 理由:类型安全、解耦键名与文本,长期维护成本低,适合中大型项目。

3. ​动态列表需求

  • 推荐方案:数组遍历写法
  • 理由:直接遍历状态列表,灵活渲染 UI(如筛选菜单、选项卡)。

4. ​国际化/多主题

  • 推荐方案:枚举 + 映射对象 + 语言包(如 i18n
  • 理由:集中管理多语言文本,轻松扩展新语言,与国际化库无缝集成。
posted @ 2025-03-20 15:55  雪旭  阅读(98)  评论(0)    收藏  举报