乐趣区

关于javascript:精读Unique-MapTypes-Construct-Tuple

解决 TS 问题的最好方法就是多练,这次解读 type-challenges Medium 难度 63~68 题。

精读

Unique

实现 Unique<T>,对 T 去重:

type Res = Unique<[1, 1, 2, 2, 3, 3]> // expected to be [1, 2, 3]
type Res1 = Unique<[1, 2, 3, 4, 4, 5, 6, 7]> // expected to be [1, 2, 3, 4, 5, 6, 7]
type Res2 = Unique<[1, 'a', 2, 'b', 2, 'a']> // expected to be [1, "a", 2, "b"]
type Res3 = Unique<[string, number, 1, 'a', 1, string, 2, 'b', 2, number]> // expected to be [string, number, 1, "a", 2, "b"]
type Res4 = Unique<[unknown, unknown, any, any, never, never]> // expected to be [unknown, any, never]

去重须要一直递归产生去重后后果,因而须要一个辅助变量 R 配合,并把 Tinfer 逐个拆解,判断第一个字符是否在后果数组里,如果不在就塞进去:

type Unique<T, R extends any[] = []> = T extends [infer F, ...infer Rest]
  ? Includes<R, F> extends true
    ? Unique<Rest, R>
    : Unique<Rest, [...R, F]>
  : R

那么剩下的问题就是,如何判断一个对象是否呈现在数组中,应用递归能够轻松实现:

type Includes<Arr, Value> = Arr extends [infer F, ...infer Rest]
  ? Equal<F, Value> extends true
    ? true
    : Includes<Rest, Value>
  : false

每次取首项,如果等于 Value 间接返回 true,否则持续递归,如果数组递归完结(不形成 Arr extends [xxx] 的模式)阐明递归完了还没有找到相等值,间接返回 false

把这两个函数组合一下就能轻松解决本题:

// 本题答案
type Unique<T, R extends any[] = []> = T extends [infer F, ...infer Rest]
  ? Includes<R, F> extends true
    ? Unique<Rest, R>
    : Unique<Rest, [...R, F]>
  : R

type Includes<Arr, Value> = Arr extends [infer F, ...infer Rest]
  ? Equal<F, Value> extends true
    ? true
    : Includes<Rest, Value>
  : false

MapTypes

实现 MapTypes<T, R>,依据对象 R 的形容来替换类型:

type StringToNumber = {
  mapFrom: string; // value of key which value is string
  mapTo: number; // will be transformed for number
}
MapTypes<{iWillBeANumberOneDay: string}, StringToNumber> // gives {iWillBeANumberOneDay: number;}

因为要返回一个新对象,所以咱们应用 {[K in keyof T]: ... } 的模式形容后果对象。而后就要对 Value 类型进行判断了,为了避免 never 的作用,咱们包一层数组进行判断:

type MapTypes<T, R extends {mapFrom: any; mapTo: any}> = {[K in keyof T]: [T[K]] extends [R['mapFrom']] ? R['mapTo'] : T[K]
}

但这个解答还有一个 case 无奈通过:

MapTypes<{iWillBeNumberOrDate: string}, StringToDate | StringToNumber> // gives {iWillBeNumberOrDate: number | Date;}

咱们须要思考到 Union 散发机制以及每次都要从新匹配一次是否命中 mapFrom,因而须要抽一个函数:

type Transform<R extends {mapFrom: any; mapTo: any}, T> = R extends any
  ? T extends R['mapFrom']
    ? R['mapTo']
    : never
  : never

为什么要 R extends any 看似无意义的写法呢?起因是 R 是联结类型,这样能够触发散发机制,让每一个类型独立判断。所以最终答案就是:

// 本题答案
type MapTypes<T, R extends {mapFrom: any; mapTo: any}> = {[K in keyof T]: [T[K]] extends [R['mapFrom']] ? Transform<R, T[K]> : T[K]
}

type Transform<R extends {mapFrom: any; mapTo: any}, T> = R extends any
  ? T extends R['mapFrom']
    ? R['mapTo']
    : never
  : never

Construct Tuple

生成指定长度的 Tuple:

type result = ConstructTuple<2> // expect to be [unknown, unkonwn]

比拟容易想到的方法是利用下标递归:

type ConstructTuple<
  L extends number,
  I extends number[] = []
> = I['length'] extends L ? [] : [unknown, ...ConstructTuple<L, [1, ...I]>]

但在如下测试用例会遇到递归长度过深的问题:

ConstructTuple<999> // Type instantiation is excessively deep and possibly infinite

一种解法是利用 minusOne 提到的 CountTo 办法快捷生成指定长度数组,把 1 替换为 unknown 即可:

// 本题答案
type ConstructTuple<L extends number> = CountTo<`${L}`>

type CountTo<
  T extends string,
  Count extends unknown[] = []
> = T extends `${infer First}${infer Rest}`
  ? CountTo<Rest, N<Count>[keyof N & First]>
  : Count

type N<T extends unknown[] = []> = {'0': [...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T]
  '1': [...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T, unknown]
  '2': [
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    unknown,
    unknown
  ]
  '3': [
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    unknown,
    unknown,
    unknown
  ]
  '4': [
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    unknown,
    unknown,
    unknown,
    unknown
  ]
  '5': [
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    unknown,
    unknown,
    unknown,
    unknown,
    unknown
  ]
  '6': [
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    unknown,
    unknown,
    unknown,
    unknown,
    unknown,
    unknown
  ]
  '7': [
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    unknown,
    unknown,
    unknown,
    unknown,
    unknown,
    unknown,
    unknown
  ]
  '8': [
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    unknown,
    unknown,
    unknown,
    unknown,
    unknown,
    unknown,
    unknown,
    unknown
  ]
  '9': [
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    ...T,
    unknown,
    unknown,
    unknown,
    unknown,
    unknown,
    unknown,
    unknown,
    unknown,
    unknown
  ]
}

Number Range

实现 NumberRange<T, P>,生成数字为从 TP 的联结类型:

type result = NumberRange<2, 9> //  | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 

NumberRange<2, 9> 为例,咱们须要实现 29 的递增递归,因而须要一个数组长度从 2 递增到 9 的辅助变量 U,以及一个存储后果的辅助变量 R:

type NumberRange<T, P, U extends any[] = 长度为 T 的数组, R>

所以咱们先实现 LengthTo 函数,传入长度 N,返回一个长度为 N 的数组:

type LengthTo<N extends number, R extends any[] = []> =
  R['length'] extends N ? R : LengthTo<N, [0, ...R]>

而后就是递归了:

// 本题答案
type NumberRange<T extends number, P extends number, U extends any[] = LengthTo<T>, R extends number = never> =
  U['length'] extends P ? (R | U['length']
  ) : (NumberRange<T, P, [0, ...U], R | U['length']>
  )

R 的默认值为 never 十分重要,否则默认值为 any,最终类型就会被放大为 any

Combination

实现 Combination<T>:

// expected to be `"foo" | "bar" | "baz" | "foo bar" | "foo bar baz" | "foo baz" | "foo baz bar" | "bar foo" | "bar foo baz" | "bar baz" | "bar baz foo" | "baz foo" | "baz foo bar" | "baz bar" | "baz bar foo"`
type Keys = Combination<['foo', 'bar', 'baz']>

本题和 AllCombination 相似:

type AllCombinations_ABC = AllCombinations<'ABC'>
// should be ''|'A'|'B'|'C'|'AB'|'AC'|'BA'|'BC'|'CA'|'CB'|'ABC'|'ACB'|'BAC'|'BCA'|'CAB'|'CBA'

还记得这题吗?咱们要将字符串变成联结类型:

type StrToUnion<S> = S extends `${infer F}${infer R}`
  ? F | StrToUnion<R>
  : never

而本题 Combination 更简略,把数组转换为联结类型只须要 T[number]。所以本题第一种组合解法是,将 AllCombinations 略微革新下,再利用 ExcludeTrimRight 删除多余的空格:

// 本题答案
type AllCombinations<T extends string[], U extends string = T[number]> = [U] extends [never]
  ? '':'' | {[K in U]: `${K} ${AllCombinations<never, Exclude<U, K>>}` }[U]

type TrimRight<T extends string> = T extends `${infer R} ` ? TrimRight<R> : T

type Combination<T extends string[]> = TrimRight<Exclude<AllCombinations<T>, ''>>

还有一种十分精彩的答案在此剖析一下:

// 本题答案
type Combination<T extends string[], U = T[number], A = U> = U extends infer U extends string
  ? `${U} ${Combination<T, Exclude<A, U>>}` | U
  : never;

仍然利用 T[number] 的个性将数组转成联结类型,再利用联结类型 extends 会分组的个性递归出后果。

之所以不会呈现结尾呈现多余的空格,是因为 U extends infer U extends string 这段判断曾经杜绝了 U 耗费完的状况,如果耗费完会及时返回 never,所以无需用 TrimRight 解决右侧多余的空格。

至于为什么要定义 A = U,在后面章节曾经介绍过了,因为联结类型 extends 过程中会进行分组,此时拜访的 U 曾经是具体类型了,但此时拜访 A 还是原始的联结类型 U

Subsequence

实现 Subsequence<T> 输入所有可能的子序列:

type A = Subsequence<[1, 2]> // [] | [1] | [2] | [1, 2]

因为是返回数组的全排列,只有每次取第一项,与残余项的递归结构出后果,| 上残余项自身递归的后果就能够了:

// 本题答案
type Subsequence<T extends number[]> = T extends [infer F, ...infer R extends number[]] ? (Subsequence<R> | [F, ...Subsequence<R>]
) : T

总结

对全排列问题有两种经典解法:

  • 利用辅助变量形式递归,留神联结类型与字符串、数组之间转换的技巧。
  • 间接递归,不借助辅助变量,个别在题目返回类型容易结构时抉择。

探讨地址是:精读《Unique, MapTypes, Construct Tuple…》· Issue #434 · dt-fe/weekly

如果你想参加探讨,请 点击这里,每周都有新的主题,周末或周一公布。前端精读 – 帮你筛选靠谱的内容。

关注 前端精读微信公众号

<img width=200 src=”https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg”>

版权申明:自在转载 - 非商用 - 非衍生 - 放弃署名(创意共享 3.0 许可证)

退出移动版