Antd 最佳实践:枚举值如何使用

本文价值:将学会前端渲染枚举的几种常见做法以及 as constsatisfies 的妙用。

打破应当废弃 TS 枚举的说法。

比如某个字段表示含义为

ts 复制代码
/**
 * 1 - 进行中
 * 2 - 排队中
 * 3 - 已完成
 * 4 - 已停止
 * 5 - 失败
 */

我们需要将其渲染在表格中同时需要渲染在详情中。

有两种写法

1. 类型优先

首先在 service 层定义其类型 ITrainStatus

ts 复制代码
// foo.service.ts
export enum ITrainStatus {
  Progressing = 1,
  Queueing = 2,
  Completed = 3,
  Stopped = 4,
  Failed = 5,
}

// 使用
export const FooService = {
  prefix: '/api/v1/foo',
  
  async list(params): Promise<Data<IListRespData>> {
    return request(`${this.prefix}/list`, { params })
  },
}

type IListRespData = {
  id: number;
  trainStatus: ITrainStatus;
  ...
}

虽然大部分情况 union 应该优先于枚举,但是此处的状态并非字符串,不具备可读性,故应该使用枚举或常量当做枚举。我们先用枚举写一遍。

如果我们使用 ProTable 可以利用其 valueEnum:

tsx 复制代码
// utils.tsx
export const TrainStatusValueEnum: Record<
  ITrainStatus | 'all',
  { text: string; status: BadgeProps['status'] }
> = {
  all: { text: '全部', status: 'default' },
  1: {
    text: '进行中',
    status: 'processing',
  },
  2: {
    text: '排队中',
    status: 'warning',
  },
  3: {
    text: '已完成',
    status: 'success',
  },
  4: {
    text: '已停止',
    status: 'default',
  },
  5: {
    text: '失败',
    status: 'error',
  },
};

export function mapStatusToBadge(status: ITrainStatus) {
  const config = TrainStatusValueEnum[status];
  return config ? (
    <Badge status={config.status} text={config.text} />
  ) : (
    <Badge status="default" text={`未知 (${status})`} />
  );
}

注意:此处类型标注 ITrainStatus | 'all' 故以后新增类型,若忘记修改此处 TS 将报错。

第一个常量给列表页用,第二个给详情页用。

列表页渲染:

tsx 复制代码
// list.tsx
[
    {
      valueType: 'select',
      valueEnum: TrainStatusValueEnum,
    },
]

详情页渲染:

tsx 复制代码
状态:{mapStatusToBadge(status)}

2. 常量当做枚举

先定义常量

ts 复制代码
// service.ts
export const TrainStatus = {
  Progressing: 1,
  Queueing: 2,
  Completed: 3,
  Stopped: 4,
  Failed: 5,
} as const;

然后从常量推导枚举用作类型标注(也仅做类型标注):

ts 复制代码
// service.ts
/**
 * 1 - 进行中
 * 2 - 排队中
 * 3 - 已完成
 * 4 - 已停止
 * 5 - 失败
 */
type ITrainStatus = typeof TrainStatus[keyof typeof TrainStatus];

有 3 个注意点:

  1. 我们导出的是常量,类型仅用在标注。
  2. 其他地方应该用导出的常量。比如:

Good:

ts 复制代码
const stopped = status === TrainStatus.Stopped;

Bad(魔法数字):

ts 复制代码
const stopped = status === 4;
  1. 常量定义增加 as const 否则 ITrainStatus 类型将为 number 而非 1 | 2 | 3 | 4 | 5 当然如果不介意可读性也没关系。

小结:和枚举没有本质区别反倒多写了代码,为了不用枚举而不用不可取。

3. UI 配置优先

接下来我们用配置写一遍。这种方式 UI 为先,其他代码可自动推导生成。

ts 复制代码
const TrainStatusConfig = {
  1: {
    text: '进行中',
    status: 'processing',
  },
  2: {
    text: '排队中',
    status: 'warning',
  },
  3: {
    text: '已完成',
    status: 'success',
  },
  4: {
    text: '已停止',
    status: 'default',
  },
  5: {
    text: '失败',
    status: 'error',
  },
} satisfies Record<number, { text: string; status: BadgeProps['status'] }>;

hover 效果:

最后加 satisfies 是为了能自动提示 status 的值。如果标注类型也能为何用 satisfies?标注类型的话 TrainStatusConfig 的 key 类型将为 number,最后推导 ITrainStatus 也将是 number 而非数字枚举,可读性将下降。

不推荐的写法(类型标注):

Bad:

ts 复制代码
const TrainStatusConfig: Record<number, { text: string; status: BadgeProps['status'] }> = {
  1: {
    text: '进行中',
    status: 'processing',
  },
  2: {
    text: '排队中',
    status: 'warning',
  },
  3: {
    text: '已完成',
    status: 'success',
  },
  4: {
    text: '已停止',
    status: 'default',
  },
  5: {
    text: '失败',
    status: 'error',
  },
};

hover 效果:

看不到内容,可读性太差

还可以这么写(增加 as const,可读性进一步增强,连 text 内容都能显示,也更安全修改将报错):

ts 复制代码
} as const satisfies Record<number, { text: string; status: BadgeProps['status'] }>;

hover 效果:

其他都可以通过其生成:

ts 复制代码
const TrainStatusValueEnum = {
  ...TrainStatusConfig,
  all: { text: '全部', status: 'default' },
}
ts 复制代码
type ITrainStatus = keyof typeof TrainStatusConfig

当然我们还得自动生成一个常量 map 用来消除魔法数字,否则只能和数字作比较可读性下降了 const stopped = status === 4

ts 复制代码
const TrainStatus = Object.entries(TrainStatusConfig).reduce((acc, [key, { text }]) => {
  acc[text] = Number(key) as ITrainStatus;
  return acc;
}, {} as Record<IText, ITrainStatus>)
// const TrainStatus: Record<"进行中" | "排队中" | "已完成" | "已停止" | "失败", 5 | 1 | 2 | 3 | 4>

type IText = (typeof TrainStatusValueEnum)[ITrainStatus]['text'];
// type IText = "进行中" | "排队中" | "已完成" | "已停止" | "失败"

使用:

ts 复制代码
const stopped = status === TrainStatus.已停止;

mapStatusToBadge 不变省略

优点:

  • 新增状态修改一处即可,其他代码自动生成

缺点:

  • service 依赖 UI 貌似不太合适
  • 只能使用中文做 key 且 TrainStatus.已停止 hover 将显示数字 union 而非精确的 4

总结:

如果服务端返回枚举无论是数字还是字符串都应该使用枚举。通过常量虽然新增状态只需修改一处,但是会降低可读性代码复杂度也会增加。

相关推荐
鱼樱前端27 分钟前
React18+pnpm+Ts+React-Router v6从0-1搭建后台系统
前端·javascript·react.js
returnShitBoy1 小时前
前端面试:React生态有哪些?
前端·react.js·面试
IT、木易1 小时前
大白话在 React 中,如何处理表单的复杂验证逻辑?
前端·javascript·react.js
祈澈菇凉10 小时前
React 中如何实现表单的受控组件?
前端·javascript·react.js
明远湖之鱼12 小时前
手把手带你实现 Vite+React 的简易 SSR 改造【含部分原理讲解】
前端·react.js·vite
崽崽的谷雨14 小时前
react实现一个列表的拖拽排序(react实现拖拽)
前端·react.js·前端框架
ziyu_jia16 小时前
React 组件测试【React Testing Library】
前端·react.js·前端框架
祈澈菇凉16 小时前
如何在 React 中实现错误边界?
前端·react.js·前端框架
撸码到无法自拔16 小时前
❤React-组件的新旧生命周期
前端·javascript·react.js·前端框架·ecmascript