ts类型挑战【十六】

324 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第18天,点击查看活动详情

题目二十三:append-argument

// template.ts
type AppendArgument<Fn, A> = any
// test-cases.ts
import { Equal, Expect } from '@type-challenges/utils'

type Case1 = AppendArgument<(a: number, b: string) => number, boolean>
type Result1 = (a: number, b: string, x: boolean) => number

type Case2 = AppendArgument<() => void, undefined>
type Result2 = (x: undefined) => void

type cases = [
  Expect<Equal<Case1, Result1>>,
  Expect<Equal<Case2, Result2>>,
]

实现一个泛型 AppendArgument<Fn, A>,对于给定的函数类型 Fn,以及一个任意类型 A,返回一个新的函数 GG 拥有 Fn 的所有参数并在末尾追加类型为 A 的参数。

type Fn = (a: number, b: string) => number
type Result = AppendArgument<Fn, boolean> 
// 期望是 (a: number, b: string, x: boolean) => number

代码实现

  • 原代码
type AppendArgument<Fn, A> = any
  • 我们要修改 Fn 的参数,就需要先将 Fn 结构出来
type AppendArgument<Fn, A> = Fn extends (...args: infer Args) => infer ReturnType
	? ...
	: ...
  • 如果不能解构出来参数和返回值,就直接返回 Fn
type AppendArgument<Fn, A> = Fn extends (...args: infer Args) => infer ReturnType
	? ...
	: Fn
  • 如果成功解构出来 Fn 的参数和返回值,将 A 添加进参数中,其余部分原样返回
type AppendArgument<Fn, A> = Fn extends (...args: infer Args) => infer ReturnType
	? (...args: [...Args, A]) => ReturnType
	: Fn

题目二十四:permutation

// template.ts
type Permutation<T> = any
// test-cases.ts
import { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<Permutation<'A'>, ['A']>>,
  Expect<Equal<Permutation<'A' | 'B' | 'C'>, ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']>>,
  Expect<Equal<Permutation<'B' | 'A' | 'C'>, ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']>>,
  Expect<Equal<Permutation<never>, []>>,
]

实现联合类型的全排列,将联合类型转换成所有可能的全排列数组的联合类型。

测试用例中,

  • 测试1:表示单个元素的全排列
  • 测试2和3:不在乎顺序
  • 测试4:never 的全排列为空数组

代码实现

  • 原代码
type Permutation<T> = any
  • never 的全排列为空数组(测试4要求)

至于为什么要使用 [T] extends [never] ,而不是使用 T extends never,我们之后再解释

type Permutation<T> = [T] extends [never]
	? []
	: ...
  • 给定另一个泛型:U。初始值设置为 T,用来保存 T 之前的内容

此时在 T extends U 中,U 代表传入的联合类型,例如:'A' | 'B' | 'C'T 则代表继承 U 的某个值,例如:"A""B""C"

type Permutation<T, U = T> = [T] extends [never]
	? []
	: (T extends U
		? [T, ...]
		: [])

可以理解为:此时的 T 已经不是之前的联合类型了,而是联合类型其中的一个类型,而 U 则是之前的联合类型

  • 递归处理剩下的值,需要排除当前已经使用过的 T
type Permutation<T, U = T> = [T] extends [never]
	? []
	: (T extends U
		? [T, ...Permutation<Exclude<U, T>>]
		: [])

由于 Permutation 返回的是一个数组,所以这里使用了扩展运算符:[T, ...Permutation<Exclude<U, T>>]

[T] extends [never]

我们回来看看为什么要使用 [T] extends [never] 而不是直接使用 T extends never 来做判断

  • 首先我们需要知道,never 是所有类型的子类型
type p1 = never extends "x" ? string : number // string
  • 再来看看如果将其放入类型函数中会如何
type P<T> = T extends "x" ? string : number
type p2 = P<never> // never

我们会发现这里传入 never 后,并没有返回三元表达式中的 stringnumber

这是因为 never 被认为是空的联合类型,也就是说,没有联合项的联合类型,所以还是满足上面的分配律。

然而因为没有联合项可以分配,所以 P 的表达式其实根本就没有执行,所以 p2 的定义也就类似于永远没有返回的函数一样,是 never 类型的

  • 防止条件判断中的分配

在条件判断类型的定义中,将泛型参数使用 [] 括起来,即可阻断条件判断类型的分配。此时,传入参数 T 的类型将被当作一个整体,不再分配

type P<T> = [T] extends ["x"] ? string : number
type p1 = P<"x" | "y"> // number
type p2 = P<never> // string