先阐述一下问题背景,我们项目中ts 的类型定义都是手写的,大致如下
enum RoleStatus {
owner = 'owner',
editor = 'editor'
}
interface {
role: RoleStatus
}
同时我们还有个集成测试框架,里面的数据类型都是 swagger 自动生成的。服务端定义字段 role 的取值范围有 'owner'、 'editor'。swagger 自动生成的类型就是
interface {
role: 'owner' | 'editor'
}
然后我在写一个 mock 数据的时候就遇到了类型冲突的问题,因为RoleStatus.owner != owner
。即使他们的真实值是一样的,我只能通过强转之类的办法。
经过调研我发现对于这个问题的讨论特别多,并且大部分声音都是赞同禁用 enum 的,甚至微软的首席架构师 Anders表示"如果 typescript 是今天创建的,我们可能不会引入它"。因为enum 会在编译后生成一个立即执行函数,这样并不好,因为可以被运行时修改,增大打包体积等
enum RoleEnum {
Owner = 'owner',
Editor = 'editor'
}
let role = RoleEnum.Owner
========
var RoleEnum;
(function (RoleEnum){
RoleEnum["owner"] = "owner"
RoleEnum["editor"] = "editor"
})(RoleEnum || (RoleEnum = {}))
let role = RoleEnum.Owner
const enum 是一个更好的方案,他会在编译的时候直接内联到原值。
const enum RoleEnum {
Owner = 'owner',
Editor = 'editor'
}
let dir: RoleEnum = RoleEnum.Owner;
====
let dir = 'owner' /* RoleEnum.Owner */
但是这并没有解决我的问题,因为我遇到的是编译前的问题
const fn = (role:RoleEnum) => role
fn(RoleEnum.Owner) // ✅
fn('owner') // ❌ 但是我期望这样也可以使用
使用字符串联合类型解决问题
type RoleType = 'owner' | 'editor'
const fn = (role:RoleType) => role
fn(RoleEnum.Owner) // ✅
fn('owner') // ✅
这样基本就解决了我遇到的问题,但是又引入了一个新的问题,之前使用 enum 可以做到一处修改,各处生效。但如果使用了字符串联合类型,因为没有引用关系,修改一个值就需要各处确认类型是否正确,我不确定现在有没有 ide 或者编辑器能做到同步修改。
为了解决这个问题可以使用静态对象的方式(学名POJO - Plain Old Javascript Object)
const Role = {
Owner: 'owner,
Editor: 'editor'
} as const
// public type util
type ObjectToEnums<T> = T[keyof T]
type RoleEnum = ObjectToEnums<typeof Role>
enum OldRoleStatus {
owner = 'owner',
editor = 'editor'
}
const fn = (role: RoleEnum) => role
fn(Role.Owner) // ✅
fn("owner") // ✅
fn(1122) // ❌ Argument of type '1122' is not assignable to parameter of type RoleEnum
fn(OldRoleStatus.Owner) // ✅
这样就彻底解决了我的问题,既可以通过Role.Owner的方式在业务开发中使用,也可以在写测试两个系统类型隔离的情况下通过字符串使用,甚至可以兼容以前的枚举方式。 并且在如果要修改字段 value,可以直接修改 Role 对象。