TypeScript 获取对象键值的联合类型

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 有两个运算符:typeofkeyof

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,愿共同进步。

相关推荐
落霞的思绪39 分钟前
CSS复习
前端·css
咖啡の猫3 小时前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲5 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5816 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路6 小时前
GeoTools 读取影像元数据
前端
ssshooter6 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
Jerry7 小时前
Jetpack Compose 中的状态
前端
dae bal8 小时前
关于RSA和AES加密
前端·vue.js
柳杉8 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化
lynn8570_blog8 小时前
低端设备加载webp ANR
前端·算法