乐趣区

关于typescript:TypeScript-infer-关键字让类型体操更灵活


title: TypeScript infer 关键字
author: 剑大瑞
category: TypeScript
tag:

  • typescript

date: 2023-07-01 12:46:31

明天咱们介绍一个 TypeScript 十分弱小的关键字 infer。

最近在练习 type-challenges。尽管在先前的博文中有介绍过 infer 关键字的应用,然而直到我在 type-challenges 中应用的时候,才领会到这个关键字的弱小之处。这个的感觉有多弱小,打个比喻:

好比你在做体操比赛的时候,人曾经飞到半空了,然而不晓得接下来该做什么动作?该用头着地还是用脚着地?这个时候你要是晓得 infer 关键字怎么用,那么它会给你答案!就是这么神奇!

接下来说闲事。

首先 infer 关键字必须联合 TypeScript 中的 条件类型 进行操作,而后联合条件分支进行类型推导。

一个简略示例:

type ArrayElement<T extends any[]> = T extends (infer Element)[] ? Element : never

ArrayElement 类型能够承受一个数组类型,而后获取数组的元素类型。比方:

type NumList = number[]
type StrList = string[]

type NumEle = ArrayElement<NumList> // number
type StrEle = ArrayElement<StrList> // string

就像下面这么操作就能够获取到数组的元素类型。

通过下面的示例,让咱们梳理下,infer 关键字做了什么?

  • infer 这个单词本意是 推断、推论 的意思。
  • 当在条件类型中应用 infer 关键字的时候,显示的申明了一个新的类型变量 Element
  • ts 联合条件操作将推断出泛型的元素类型 赋值 给了 Element

整个过程就是这么简略。

infer 关键字不同状况下的应用

对字符串进行解构推断。

在 js 中 string 也是可迭代,解构的。利用这一个性,咱们能够应用 infer 关键字获取到 string 类型的不同地位的字符串类型。

比方咱们须要一个能够获取字符串类型的首个字符的类型工具类型 GetFirstStr

type Name = 'Da Rui'

type Result = GetFirstStr<Name> // 冀望获取 D

冀望 Result = 'D'。则应用 infer 联合条件类型操作能够实现为:

type GetFirstStr<T extends string> = T extends `${infer R}${infer O}` ? R : never

type Result = GetFirstStr<Name> // D

利用此个性,咱们能够轻松实现一个 能够将字符串类型转为首字母大写的工具类型 Capitalize<T>

type Darui = Capitalize<'da Rui'> // 冀望失去的后果 Da Rui

咱们只需将面的 GetFirstStr 稍作批改就能够,在推断获取到第一个字符后,对字符应用工具类型 Uppercase<T> 即可:

type Capitalize<T extends string> = T extends `${infer F}${infer O}` ? `${Uppercase<F>}${O}` : S;
type Darui = Capitalize<'da Rui'> // Da Rui

你能够在 playground 中试一试。就是这么奇妙。

相似的 type-challenges:

  • TrimLeft:https://github.com/type-challenges/type-challenges/blob/main/questions/00106-medium-trimleft/README.md
  • Tirm: https://github.com/type-challenges/type-challenges/blob/main/questions/00108-medium-trim/README.md
  • Replace: https://github.com/type-challenges/type-challenges/blob/main/questions/00116-medium-replace/README.md
  • StartWith: https://github.com/type-challenges/type-challenges/blob/main/questions/02688-medium-startswith/README.md
  • EndWith: https://github.com/type-challenges/type-challenges/blob/main/questions/02693-medium-endswith/README.md

对数组进行推断

在刚开始的时候咱们通过 ArrayElement<T> 来获取数组元素类型。

infer 的弱小之处在于能够让你获取数组类型任意索引地位的元素类型。

实现一个 FirstEle 获取第一个索引地位的元素类型:

type FirstEle<T extends unknown[]> = T extends [infer F, ...unknown[]] ? typeof F : never

type Result = FirstEle<[1, 2, 3]> // 1

type Result2 = FirstEle<[true, 1, 'darui']> // true

同样的实现 LastEle

type LastEle<T extends unknown[]> = T extends [...unknown[], infer L] ? typeof L : never

实现一个去头去尾 MiddleEles

type MiddleEles<T extends unknown[]> = T extends [infer F, ...infer M, infer L] ? M: never

type Result = MiddleEles<[1, 2, 3, 4, 5]> // [2, 3, 4]
type Result2 = MiddleEles<[true, 1, 2, 3, 'darui']> // [1, 2, 3]

有了三个根底的思路,咱们能够再联合递归,玩出更多花色来:

比方 type-challenges 中的 Flatten 类型,能够实现将嵌套数组类型拍平:

type flatten = Flatten<[1, 2, [3, 4], [[[5]]]]> // [1, 2, 3, 4, 5]

实现 Flatten

type Flatten<S extends unknown[], T extends unknown[] = []> =  S extends [infer X, ...infer Y] ? 
  X extends unknown[] ?
   Flatten<[...X, ...Y], T> : Flatten<[...Y], [...T, X]> 
  : T

相似的 type-challenges:

  • Last of Array: https://github.com/type-challenges/type-challenges/blob/main/questions/00015-medium-last/README.md
  • Pop: https://github.com/type-challenges/type-challenges/blob/main/questions/00016-medium-pop/README.md
  • Shift: https://github.com/type-challenges/type-challenges/blob/main/questions/03062-medium-shift/README.md
  • Reverse:https://github.com/type-challenges/type-challenges/blob/main/questions/03192-medium-reverse/README.md

对函数进行类型推断

在官网中有一个工具类型 ReturnType 能够用于获取函数的返回值类型。

type T0 = ReturnType<() => string>; // string
type T1 = ReturnType<(s: string) => void>; // void
type T2 = ReturnType<<T>() => T>; // unknown

其实它的实现也非常简单:

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

另外咱们还能够对函数的参数进行类型推断,实现一个 Parameters 获取函数参数类型

type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? P : never

const foo = (arg1: string, arg2: number): void => {}

type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]

咱们晓得通过 ... 操作符,能够将函数参数转为一个数组类型。如果联合咱们下面数组局部的操作,又能够玩出很多花色来,比方实现一个对函数参数类型进行扩大的工具类型:

type Fn = (a: number, b: string) => number

type Result = AppendArgument<Fn, boolean> 
// expected be (a: number, b: string, x: boolean) => number

实现:

type AppendArgument<F extends (...args: any[]) => any, T> = Fn extends (...args: infer P[]) => infer R ? (...args: [...P, T]) => R : never 

相似的 type-challenges:

  • Flip Arguments:https://github.com/type-challenges/type-challenges/blob/main/questions/03196-medium-flip-arguments/README.md

总结

在我看来,infer 关键字给了用户一个类型操作窗口,这个窗口能够在进行类型操作时,通过 infer 基于条件类型操作,去动静申明一个新的类型变量。而剩下的工作交给了 ts 编译器,有 ts 编译器去推断出类型变量的类型,而后给用户应用。

最初冀望有播种的敌人能够去练一练 type-challenges,并给个 star,这个仓库的确挺好玩。

欢送关注我的微信公众号:【coder 狂想曲】

  • type-challenges: https://github.com/type-challenges/type-challenges
退出移动版