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

总结:

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

相关推荐
中微子2 小时前
React Router 源码深度剖析解决面试中的深层次问题
前端·react.js
中微子3 小时前
React Router 面试指南:从基础到实战
前端·react.js·前端框架
前端_学习之路4 小时前
React--Fiber 架构
前端·react.js·架构
coderlin_4 小时前
BI布局拖拽 (1) 深入react-gird-layout源码
android·javascript·react.js
甜瓜看代码4 小时前
1.
react.js·node.js·angular.js
伍哥的传说4 小时前
React 实现五子棋人机对战小游戏
前端·javascript·react.js·前端框架·node.js·ecmascript·js
Misha韩5 小时前
React Native 一些API详解
react native·react.js
小李飞飞砖5 小时前
React Native 组件间通信方式详解
javascript·react native·react.js
小李飞飞砖5 小时前
React Native 状态管理方案全面对比
javascript·react native·react.js