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

相关推荐
用户0595401744614 分钟前
大模型长记忆评测踩坑实录:2000条错位记忆,让我排查了整整3小时
前端·css
码林鼠16 分钟前
webpack的基本配置
前端·webpack·node.js
用户938515635071 小时前
React Router 进阶:路由守卫、登录鉴权与状态传递
前端·javascript·全栈
kyriewen1 小时前
我重写了自己用了两年的防抖节流Hook——发现里面藏着3个隐藏bug
前端·javascript·面试
阿祖zu1 小时前
芝士就是力量!开源私有化部署与 GitHub 双向同步的个人知识笔记 App
前端·后端·ios
漂流瓶jz1 小时前
Webpack开发环境:观察模式/webpack-dev-server/HMR热更新
前端·javascript·webpack
IT_陈寒1 小时前
SpringBoot自动配置失效?这个隐藏配置坑了我一整晚
前端·人工智能·后端
凌涘2 小时前
前端路由(一):二十行代码实现 Hash 路由
前端
勾勾圈圈蛋蛋2 小时前
黑马Vue_day11:Vue3的watch,ref和reactive,vue2->vue3,defineOptions、Model
前端
AlloyTeamZy2 小时前
我发现,一个人做小游戏最难的,根本不是写代码
前端·人工智能·程序员