【Typescript】实际开发中常用进阶技巧

问题是什么?

TS 的进阶部分------类型操作,到底哪些部分是在业务开发中用得上的技巧?我们来列举实际问题来看看。

类型变换

「枚举」变成「联合」

当我们制作组件的时候,为了避免重复,一些字符类型的变量,用枚举来创建是十分合适的。比如一个日期组件里定义星期一到三:

ini 复制代码
enum Weekday {
  MON = 'monday',
  TUE = 'tuesday',
  WED = 'wednesday'
}

这样无论在渲染还是计算的时候,我们都能用 Weekday.MON 来避免重复和写错单词。但是在使用组件的时候,导出的属性却不能正确地提示类型:

kotlin 复制代码
interface DayProps{
  name: Weekday
}
​
<Day name='monday'/> // ⚠️ String 'monday' cannot be used to enum type Weekday

此时,除了从组件库导出 Weekday 的方式之外,还能通过创建"字符串字面量联合类型(String literal union type from enum)"的方式解决:

ini 复制代码
interface DayProps{
  name: `${Weekday}`
}
​
<Day name='monday'/> // Day.name: ('monday'|'tuesday'|'wednesday')

「对象」变成「联合」

单个配置可以用枚举代替对象,但如果是多个配置合成一个配置的时候,就只能用对象了。比如

yaml 复制代码
const workdays = {
  Mon: 1,
  Tue: 2,
  Wed: 3,
  Thu: 4,
  Fri: 5
}
const weekends = {
  Sat: 6,
  Sun: 7
}
​
const weekdays = { ...workdays, ...weekends }

然后要正确地提示到"星期几"的值,可以先用 keyof 封装一个 valueOf<T>,方便我们的操作。

ini 复制代码
type valueOf<T> = T[keyof T];
type Weekday = valueOf<typeof weekdays> // Weekday: string

从上面可以看到,Weekday 只解析成了 string,并不是我们期待的,精确的取值范围 1|2|3...|7。原来是 TS 只能对 readonly 的类型或者数据进行精确解析,所以我们需要定义变量的时候,声明它们是只读的类型。

rust 复制代码
const workdays = {
  Mon: 1,
  Tue: 2,
  Wed: 3,
  Thu: 4,
  Fri: 5
} as const //  转变为"只读"类型,让其值可以被正确地解析
​
const weekends = {
  Sat: 6,
  Sun: 7
} as const
​
const weekdays = { ...workdays, ...weekends }
type Weekday = valueOf<typeof weekdays> // Weekday: (1|2|3|4|5|6|7)

「数组」变成「interface」

当我们创建一个,各种元素并不怎么相关,仅仅只是用途相同的集合的时候,会用到字符串数组。比如 icon 图片的数组

javascript 复制代码
const icons = ['banana.png', 'avata.svg', 'water.jpg']
​
// 由于图片自带名字,所以直接来生成对象使用
const iconCollection = icons.reduce((acc, path) => {
  const name = path.split('.')[0]
  return Object.assign(acc, { [name]: path })
}, Object())
​
/*{
  banana: "banana.png",
  avata: "avata.svg",
  water: "water.jpg"
}*/

然后我们想正确地提示 iconCollection 的类型,是否可以用上面 as const 的技巧,把它转换成只读类型呢?

这是不行的!因为它是动态地生成的对象,无法被静态地解析。要解析,只能是对静态的 icons 数组下手,把它转换成 interface。

typescript 复制代码
const icons = ['banana.png', 'avata.svg', 'water.jpg'] as const // Trans to readonly
​
type SplitName<T> = T extends `${infer P}.${string}` ? P : never; // 利用类型推导(infer),得到文件名 P
type IconCollection = Record<SplitFileName<(typeof icons)[number]>, string> // IconCollection: {banana: string, avata: string, water: string}

函数的精确类型提示

重载

一个好的函数,最好就是单一职责,且一种输入,对应一种输出。但有时候确实会有,一个功能处理不同数据类型的情况,比如以下这个函数

typescript 复制代码
/**
 * 改变参数类型,数字转字符串,字符串则转数字
 * @param x
 */
function changeType(x: string|number): number|string {
  return typeof x === 'string' ? Number(x) : String(x)
}

这个类型声明虽然没有错误,但并不能得到精准的提示。我们希望类型提示,与函数描述完全一致,这时候重载就上场了。

scss 复制代码
function changeType(x: number): string;
function changeType(x: string): number;
/**
 * 改变参数类型,数字转字符串,字符串则转数字
 * @param x
 */
function changeType(x) {
  return typeof x === 'string' ? Number(x) : String(x)
}
​
changeType(123) // function changeType( x: number): string
changeType('456') // function changeType( x: string): number

类型分发

继续沿用上面的例子,我们可以用类型分发(distribution)来根据参数类型,推导输出类型。

typescript 复制代码
/**
 * 改变参数类型,数字转字符串,字符串则转数字
 * @param x
 */
function changeType<T>(x: T): T extends number ? string : T extends string ? number : never {
  return typeof x === 'string' ? Number(x) : String(x)
}

最后

我发现的业务项目中常见的 TS 进阶用法就以上这些,大家还有什么补充的呢?欢迎评论。

相关推荐
FanetheDivine15 小时前
ts中如何描述一个复杂函数的类型
前端·typescript
struggle20252 天前
AxonHub 开源程序是一个现代 AI 网关系统,提供统一的 OpenAI、Anthropic 和 AI SDK 兼容 API
css·人工智能·typescript·go·shell·powershell
执剑、天涯2 天前
通过一个typescript的小游戏,使用单元测试实战(二)
javascript·typescript·单元测试
chéng ௹2 天前
Vue3+Ts+Element Plus 权限菜单控制节点
前端·javascript·vue.js·typescript
武清伯MVP3 天前
阮一峰《TypeScript 教程》学习笔记——基本用法
笔记·学习·typescript
ttod_qzstudio4 天前
解决 Vue 3 + TypeScript 中 v-for 循环类型推断问题
前端·vue.js·typescript
今天头发还在吗4 天前
【React】动态SVG连接线实现:图片与按钮的可视化映射
前端·javascript·react.js·typescript·前端框架
冷冷的菜哥5 天前
react多文件分片上传——支持拖拽与进度展示
前端·react.js·typescript·多文件上传·分片上传
Kisang.5 天前
【HarmonyOS】窗口管理实战指南
前端·华为·typescript·harmonyos·鸿蒙
Dajiaonew6 天前
Vue3 + TypeScript 一篇文章 后端变全栈
前端·javascript·typescript