前几天排查项目性能问题,发现一个让人哭笑不得的情况。光是TypeScript
的enum
就占了打包体积的相当一部分!
同事小张一脸无辜地说:"我用enum
不是为了类型安全嘛..."
没错,enum
确实能保证类型安全,但代价有点大了!我尝试了一种新方法后,打包体积直接减少了20%,而且类型安全一点都没少!
为什么不用enum?
先看个我们项目里的真实例子:
typescript
// 以前的写法 - enum
enum UserRole {
Admin = 'admin',
User = 'user',
Guest = 'guest',
// ...后面还有十来个角色
}
// 使用的时候
const role = UserRole.Admin
看起来很正常对不对?但你知道编译成JavaScript
后发生了什么吗?
javascript
// 编译后的代码
var UserRole;
(function (UserRole) {
UserRole["Admin"] = "admin";
UserRole["User"] = "user";
UserRole["Guest"] = "guest";
// ...每个值都会生成对应的代码
})(UserRole || (UserRole = {}));
每个enum
都会变成一个立即执行函数,项目里enum
越多,生成的代码就越多,打包体积自然就膨胀了。
新的解决方案:常量对象 + 类型别名
来看看我是怎么改造的:
typescript
// 新写法 - 常量对象
const UserRole = {
Admin: 'admin',
User: 'user',
Guest: 'guest',
} as const // as const很重要,让TypeScript知道这是只读的
// 类型别名 - 相当于枚举的类型!
type UserRole = keyof typeof UserRole
// 使用时的方式完全一样!
const role: UserRole = UserRole.Admin
as const
是关键!它告诉TypeScript
:"这个对象的值都是固定的,不可用动!"
类型别名是什么意思?
类型别名就是给类型起个新名字,比如这里的type UserRole = keyof typeof UserRole
,意思是:
typeof UserRole
:获取UserRole对象的类型keyof
:取这个对象的所有键(Key)- 所以最终
UserRole
类型就是"Admin" | "User" | "Guest"
这样的联合类型
写代码会有枚举一样的提示吗?
有的!完全一样! 当你输入UserRole.
的时候,VSCode还是会自动提示Admin
、User
、Guest
,和用enum
时一模一样。
编译后发生了什么?
javascript
// 编译后的代码 - 简洁多了!
const UserRole = {
Admin: 'admin',
User: 'user',
Guest: 'guest',
}
就这么简单! 没有额外的函数,没有运行时开销,就是普通的对象字面量。
实际效果
在我们项目中,替换了所有的enum
:
改造前: 打包体积:1.2MB,enum数量:27个 改造后: 打包体积:960KB,enum数量:0个
体积直接减少了20%! 而且代码提示、类型检查完全没受影响。
再来看看几个案例
1. 页面状态管理
typescript
// 替换前
enum PageStatus {
Loading = 'loading',
Success = 'success',
Error = 'error',
Empty = 'empty'
}
// 替换后
const PageStatus = {
Loading: 'loading',
Success: 'success',
Error: 'error',
Empty: 'empty'
} as const
type PageStatus = keyof typeof PageStatus
// 使用体验完全一致!
function renderPage(status: PageStatus) {
// ...
}
renderPage(PageStatus.Loading) // ✅ 代码提示依旧完美
2. 主题配置
typescript
// 主题配置
const Theme = {
Light: 'light',
Dark: 'dark',
Auto: 'auto'
} as const
type Theme = keyof typeof Theme
// 使用时照样有提示
function setTheme(theme: Theme) {
console.log(`切换至${theme}主题`)
}
setTheme(Theme.Dark) // ✅ 输入Theme.就会自动提示
3. API错误码
typescript
// 错误码定义
const ErrorCode = {
NotFound: 404,
Unauthorized: 401,
ServerError: 500
} as const
type ErrorCode = typeof ErrorCode[keyof typeof ErrorCode]
// 使用
if (error.code === ErrorCode.NotFound) {
// 处理404错误
}
需要注意的地方
虽然这个方案很好,但有两点要注意:
数字枚举需要稍作调整:
typescript
const StatusCode = {
Ok: 200,
NotFound: 404
} as const
type StatusCode = typeof StatusCode[keyof typeof StatusCode]
如果需要反向映射(根据值找键),需要自己写工具函数:
typescript
function getKeyByValue(obj: any, value: any) {
return Object.keys(obj).find(key => obj[key] === value)
}
或者还是直接用枚举
typescript
enum Status {
Draft = 1,
Published = 2
}
const statusName = Status[1] // 返回 "Draft"
总结
简单来说就是:
- 用
const obj = { ... } as const
代替enum
- 用
type Key = keyof typeof obj
定义类型 - 打包体积能小很多,构建速度也更快,代码更简洁
PS :这个方法需要TypeScript 3.4+
版本支持。
觉得有帮助的话,点个收藏分享给更多小伙伴吧~还有什么问题欢迎留言讨论!
我是大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!
📌往期精彩
《工作 5 年没碰过分布式锁,是我太菜还是公司太稳?网友:太真实了!》
《写给小公司前端的 UI 规范:别让页面丑得自己都看不下去》
《只会写 Mapper 就敢说会 MyBatis?面试官:原理都没懂》