hello,大家好,我是 SuperYing。今天我们来聊聊 "TS 如何将对象的值作为联合类型"。
背景
最近在做项目的时候遇到一个场景:
封装了一个 Transition
组件,使用时需要传递 name
属性,以明确对应动画效果。组件内置了几种动画效果,并维护成一个对象,如下:
javascript
const TRANSITION_NAME = {
FADE: 'fade', // 消退
FADE_SLIDE: 'fade-slide', // 滑动
FADE_BOTTOM: 'fade-bottom', // 底部消退
FADE_SCALE: 'fade-scale', // 缩放消退
ZOOM_FADE: 'zoom-fade', // 渐变
ZOOM_OUT: 'zoom-out' // 闪现
}
为了确保组件使用者能够明确并准确的传递 name
属性值,就需要限制 name
属性的 TS 类型
。
那么问题来了,怎么才能做到 name
的类型与 TRANSITION_NAME
的属性值一致呢?
尝试
1.笨方法
直接手动声明一个 TRANSITION_NAME
对象属性值一致的联合类型:
typescript
type TRANSITION_NAME_TYPE = 'fade' | 'fade-slide' | 'fade-bottom' | 'fade-scale' | 'zoom-fade' | 'zoom-out'
但是这样有一个很明显的问题,一旦调整 TRANSITION_NAME
对象的属性值,类型 TRANSITION_NAME_TYPE
也需要手动调整。是否能够同步完全取决于开发人员,相当不保险。
2.继续研究
TypeScript
有两个运算符:typeof
和 keyof
。
keyof
运算符接受一个对象类型,并生成其键的字符串或数字字面量并集。
如:
typescript
type Point = { x: number; y: number };
type P = keyof Point; // P 的类型为 'x' | 'Y'
typeof
运算符可以在类型上下文中引用变量或属性的类型。
如:
typescript
let s = "hello";
let n: typeof s; // n 类型为 string
综上我们考虑结合这两种运算符对对象类型进行处理:
typescript
// 对象类型
type TRANSITION_NAME_OBJ_TYPE = typeof TRANSITION_NAME
// 对象键值类型
type TRANSITION_NAME_KEY_TYPE = keyof TRANSITION_NAME_OBJ_TYPE
// 最终的对象属性值类型
type TRANSITION_NAME_TYPE = TRANSITION_NAME_OBJ_TYPE[TRANSITION_NAME_KEY_TYPE]
经过以上三步,我们貌似取到了对象属性值类型的联合类型,赶紧测试一下:
what???为什么类型会是 string
,而不是联合类型呢?
3.持续深入
第 2 步的思路肯定是没问题的,取对象类型的键值属性。那么为什么结果却事与愿违呢,拿到的不是键值的联合类型,而是键值的类型 string
?
经过对官方文档的一番啃食,我发现了 const 断言 ,它的作用之一就是防止文字类型被扩展。
例如:
typescript
// 不添加 as const 时,a 的类型会被推断为 string
// 添加 as const 断言后,a 的类型会被推断为 'hello'
const a = 'hello' as const
那么我们这里能不能用 const 断言 呢?答案是 yes。我们直接给 TRANSITION_NAME
对象添加 const 断言,然后再如第 2 步同样的处理,来看下效果:
typescript
const TRANSITION_NAME = {
FADE: 'fade', // 消退
FADE_SLIDE: 'fade-slide', // 滑动
FADE_BOTTOM: 'fade-bottom', // 底部消退
FADE_SCALE: 'fade-scale', // 缩放消退
ZOOM_FADE: 'zoom-fade', // 渐变
ZOOM_OUT: 'zoom-out' // 闪现
} as const
如图所示,可以发现 TRANSITION_NAME_TYPE
类型拿到了 TRANSITION_NAME
对象键值的联合类型,且赋值为 '123' 时 ts 校验会报错 "不能将类型""123""分配给类型 TRANSITION_NAME_TYPE。
4.优化
第 2 步的逻辑我们可以提取为以下的公共类型,后续类型场景可以复用:
typescript
type ValueOf<T> = T[keyof T]
本文示例代码调整如下:
typescript
type TRANSITION_NAME_TYPE = ValueOf<typeof TRANSITION_NAME>
搞定 !!!
总结
keyof
运算符可以是生成对象键值字面量的并集。typeof
运算符可以引用变量或属性的类型。const
断言可以防止文字类型被扩展。type ValueOf<T> = T[keyof T]
,通用的获取对象键值联合类型的方式,前提是相关对象使用了const
断言,否则会被扩展为键值类型的联合类型。
感谢阅读,我是 SuperYing,愿共同进步。