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 )
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。