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

相关推荐
@大迁世界3 分钟前
构建 Next.js 应用时的安全保障与风险防范措施
开发语言·前端·javascript·安全·ecmascript
IT、木易1 小时前
ES6 新特性,优势和用法?
前端·ecmascript·es6
计算机软件程序设计1 小时前
vue和微信小程序处理markdown格式数据
前端·vue.js·微信小程序
指尖时光.2 小时前
【前端进阶】01 重识HTML,掌握页面基本结构和加载过程
前端·html
前端御书房2 小时前
Pinia 3.0 正式发布:全面拥抱 Vue 3 生态,升级指南与实战教程
前端·javascript·vue.js
NoneCoder2 小时前
JavaScript系列(84)--前端工程化概述
前端·javascript·状态模式
晚安7202 小时前
idea添加web工程
java·前端·intellij-idea
零凌林3 小时前
vue3中解决组件间 css 层级问题最佳实践(Teleport的使用)
前端·css·vue.js·新特性·vue3.0·teleport
糟糕好吃3 小时前
用二进制思维重构前端权限系统
前端
拉不动的猪4 小时前
刷刷题17(webpack)
前端·javascript·面试