3

The best way to solve the TS problem is to practice more. This time, the difficulty of interpreting type-challenges Medium is 63~68 questions.

intensive reading

Unique

Implement Unique<T> , yes T deduplication:

 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]

Deduplication needs to recursively generate the result after deduplication, so it needs an auxiliary variable R to cooperate, and disassemble T with infer one by one to judge the first one Whether the character is in the result array, and if not, stuff it:

 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

The remaining question then is how to tell if an object is present in an array, which can be easily done using recursion:

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

Each time the first item is taken, if it is equal to Value and directly return true , otherwise continue to recurse, if the array recursion ends (not in the form of Arr extends [xxx] ), it means that the recursion is over. If no equal value is found, return false directly.

This problem can be easily solved by combining these two functions:

 // 本题答案
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

Implement MapTypes<T, R> , replace the type according to the description of the object 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; }

Since we are returning a new object, we describe the resulting object in the form { [K in keyof T]: ... } . Then we have to judge the Value type. In order to prevent the effect of never , we wrap a layer of array to judge:

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

But there is one more case that this solution fails:

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

We need to take into account the Union distribution mechanism and whether to rematch every time it hits mapFrom , so we need to extract a function:

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

Why R extends any seems to be meaningless? The reason is that R is a joint type, which can trigger the distribution mechanism and allow each type to be judged independently. So the final answer is:

 // 本题答案
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

Generate a Tuple of the specified length:

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

The easiest way to think of it is to use subscript recursion:

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

But in the following test cases, the recursion length is too deep:

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

One solution is to use the --- 382fa726397fe3b6c1e09c54f955cf55 CountTo method mentioned by minusOne to quickly generate an array of specified length, and replace 1 with 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

Implements NumberRange<T, P> and generates numbers as union types from T to P :

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

Taking NumberRange<2, 9> as an example, we need to implement the incremental recursion from --- 2 to 9 , so we need to increment an array length from 2 to 9 5a678ff4c00b56bd49 An auxiliary variable for U 9 and an auxiliary variable for storing the result R :

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

So we first implement the LengthTo function, pass in the length N , and return an array of length N :

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

Then there is recursion:

 // 本题答案
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']>
  )

The default value of ---4c6e0373d789921a07835ff535d17d7d R is never very important, otherwise the default value is any , and the final type will be enlarged to any

Combination

Implementation 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']>

This question is similar to AllCombination :

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

Remember this question? We're going to turn the string into a union type:

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

And this question Combination is simpler, converting an array to a union type only requires T[number] . So the first combined solution to this problem is to slightly transform AllCombinations , and then use Exclude and TrimRight to delete the extra spaces:

 // 本题答案
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>, ''>>

There is also a very wonderful answer to analyze here:

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

Still use the feature of T[number] to convert the array into a union type, and then use the union type extends to recurse the result of grouping.

The reason why there is no extra space at the end is because U extends infer U extends string this judgment has been eliminated U consumed, if it is exhausted, it will return in time never , so there is no need to use TrimRight to deal with the extra spaces on the right.

As for why to define A = U , it has been introduced in the previous chapter, because the union type extends will be grouped in the process, and the access at this time U is already specific type, but this time access A is still the original union type U .

Subsequence

Implementation Subsequence<T> outputs all possible subsequences:

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

Because it is the full permutation of the returned array, as long as the first item is taken each time, the result is constructed recursively with the remaining items, | The recursive result of the remaining items itself is fine:

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

Summarize

There are two classical solutions to the full permutation problem:

  • Use auxiliary variable recursion, pay attention to the skills of converting between union types and strings and arrays.
  • Direct recursion, without the aid of auxiliary variables, is generally selected when the return type of the question is easy to construct.
The discussion address is: Intensive Reading "Unique, MapTypes, Construct Tuple..." Issue #434 dt-fe/weekly

If you'd like to join the discussion, click here , there are new topics every week, with a weekend or Monday release. Front-end intensive reading - help you filter reliable content.

Follow Front-end Intensive Reading WeChat Official Account

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

Copyright notice: Free to reprint - non-commercial - non-derivative - keep attribution ( Creative Commons 3.0 license )

黄子毅
7k 声望9.5k 粉丝