持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第23天,点击查看活动详情
前言
在学习typescript的过程当中,有一个github库对其类型的学习特别有帮助,是一个有点类似于leetcode的刷题项目,能够在里面刷各种关于typescript类型的题目,在上一篇文章中,我们完成了中等的第十六题,今天来做中等的第十七题 296-medium-permutation
下面这个是类型体操github仓库:
296-medium-permutation
import type { 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<boolean>, [false, true] | [true, false]>>,
Expect<Equal<Permutation<never>, []>>,
]
从README和测试用例中能够得出,我们需要实现一个工具函数 Permutation 能够将一个联合类型转换为数组,并且这个数组是由联合类型组成的所有可能性。
实现 Permutation
在做这道题之前,我们需要先来了解,如果合理的利用分布式条件类型来把联合类型转为数组:
type Permutation<T> = [T] extends any[]
? [T]
: []
type PERMUTA1 = Permutation<'A' | 'B' | 'C'>
// type PERMUTA1 = ["A" | "B" | "C"]
单次的分布式条件类型就能够把联合类型转为数组,并且我们需要的是所有的组合情况,那么是不是可以通过第一次分布式拿到联合类型的每一个值:
type Permutation<T> = [T] extends any[]
? T extends T? [T]:[]
: []
type PERMUTA1 = Permutation<'A' | 'B' | 'C'>
// type PERMUTA1 = ["A"] | ["B"] | ["C"]
然后通过递归和之前学习过的 Exclude 工具方法,我们就能够拿到每一项的不同值,并且将剩下的值丢入递归,再次进行分布式联合,就可以再次拿到每一项,这样子就能够实现题目的要求了。
但是做起来的时候就会发现,上面的 T 在条件里面已经因为分布式联合变成了联合类型中固定的某一项,而不是整体,那怎么办呢,这里就可以在定义一个新的泛型参数,用于在一开始的时候复制一份 T 以便于方便我们后面在排除掉当前项的时候可以用到。
type Permutation<T, K=T> = [T] extends any[]
? K
: []
type PERMUTA1 = Permutation<'A' | 'B' | 'C'>
// type PERMUTA1 = "A" | "B" | "C"
可以看到,哪怕是进过了分布式联合类型,但是因为是对 T 的分布式联合,所以在extends的子句中,只有T取到的是当前的每一项,K 依然是联合类型。
type Permutation<T, K=T> = [T] extends any[]
? T extends T? [T,...Permutation<Exclude<K,T>>]:[]
: []
type PERMUTA1 = Permutation<'A' | 'B' | 'C'>
// type PERMUTA1 = never
但是这样做之后,我们会发现,最后拿到的确实一个 never,这是为什么呢,测试发现:
type ASAD = Exclude<1,1>
// type ASAD = never
Exclude 工具方法在传入两个同样的类型的时候,会返回 never ,但是很明显上面我们并没有对 never做出处理,所以会导致最后取得的值变为 never,那么既然入参会存在 never 的情况,我们就需要改变一下判断条件,最开始的条件不再是任何数组,而是判断是否是一个保存着 never 的数组:
type Permutation<T, K = T> = [T] extends [never]
? []
: T extends T
? [T, ...Permutation<Exclude<K, T>>]
: never;
到这里就会发现,测试用例就已经全部通过了。
知识点
关于上述提到了部分的知识点:
Rest参数infer关键字Exclude工具类型- 分布式条件类型的灵活使用
今天这道题对于分布式条件类型要求有着比较深刻的理解,灵活使用分布式条件类型能够对联合类型做出很多多样化的操作,对于分布式条件类型还不熟悉的,可以看一下之前的文章,并且自己多尝试一下,看一下在不同的情况下分布式条件类型会怎么样运算。
总结
今天我们做完了中等的第十七题,题目可能会涉及到比较难得逻辑部分,但是只要能够一步一步的去思考,TS解决复杂类型的方式一般就是通过条件类型和递归,我们可以优先考虑一些边界条件,然后在通过边界条件去扩展更多的情况,这也是递归惯用的思路所在。