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

相关推荐
北海-cherish2 小时前
vue中的 watchEffect、watchAsyncEffect、watchPostEffect的区别
前端·javascript·vue.js
2501_915909063 小时前
HTML5 与 HTTPS,页面能力、必要性、常见问题与实战排查
前端·ios·小程序·https·uni-app·iphone·html5
white-persist4 小时前
Python实例方法与Python类的构造方法全解析
开发语言·前端·python·原型模式
新中地GIS开发老师5 小时前
Cesium 军事标绘入门:用 Cesium-Plot-JS 快速实现标绘功能
前端·javascript·arcgis·cesium·gis开发·地理信息科学
Superxpang5 小时前
前端性能优化
前端·javascript·vue.js·性能优化
Rysxt_5 小时前
Element Plus 入门教程:从零开始构建 Vue 3 界面
前端·javascript·vue.js
隐含5 小时前
对于el-table中自定义表头中添加el-popover会弹出两个的解决方案,分别针对固定列和非固定列来隐藏最后一个浮框。
前端·javascript·vue.js
大鱼前端5 小时前
Turbopack vs Webpack vs Vite:前端构建工具三分天下,谁将胜出?
前端·webpack·turbopack
你的人类朋友5 小时前
先用js快速开发,后续引入ts是否是一个好的实践?
前端·javascript·后端