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

相关推荐
谭光志5 小时前
Vibe Coding 时代,程序员该何去何从
前端·后端·ai编程
仿生狮子7 小时前
别再说“全栈”了,AI 时代团队只认这 5 种人
前端·人工智能·后端
kyriewen7 小时前
AI 写的 React 组件,合并前必查的 6 个坏味道——每个都有代码对比
前端·javascript·react.js
Highcharts.js7 小时前
软件开发公司为什么选择 Highcharts?
前端·前端框架·echarts·数据可视化·技术选型·highcharts·图表工具
阿祖zu8 小时前
企业 AI 转型之痛,个人提效十倍不止,组织效率仍止步不前?
前端·aigc·agent
摇滚侠9 小时前
SpringBoot3+Vue3 全套视频教程 45-51
前端·javascript·vue.js
酸梅果茶9 小时前
【6】lightning_lm项目-LIO 前端 -点云预处理模块
前端·slam
Hilaku9 小时前
前端大裁员背后的恐慌:我们还剩什么底牌?
前端·javascript·程序员
咖啡无伴侣10 小时前
unplugin-auto-import 使用详解+面试高频考点
前端·架构
搬砖记录员10 小时前
录屏文件打不开?H.265兼容性踩坑与4种实战方案
前端