The best way to solve the TS problem is to practice more. This time, I will interpret the difficulty of type-challenges Medium questions 9~16.
intensive reading
Promise.all
Implement the function PromiseAll
, input PromiseLike, and output Promise<T>
, of which T
is the input parsing result:
const promiseAllTest1 = PromiseAll([1, 2, 3] as const)
const promiseAllTest2 = PromiseAll([1, 2, Promise.resolve(3)] as const)
const promiseAllTest3 = PromiseAll([1, 2, Promise.resolve(3)])
The difficulty of this question is not Promise
how to deal with it, but rather { [K in keyof T]: T[K] }
it is also applicable to describing arrays in TS, which is something that JS players can never think of:
// 本题答案
declare function PromiseAll<T>(values: T): Promise<{
[K in keyof T]: T[K] extends Promise<infer U> ? U : T[K]
}>
I don't know if it's a bug or a feature, but TS's { [K in keyof T]: T[K] }
can be compatible with tuple, array and object types at the same time.
Type Lookup
Implements LookUp<T, P>
, finds type
from union type T
and returns the union cd of P
interface Cat {
type: 'cat'
breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal'
}
interface Dog {
type: 'dog'
breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer'
color: 'brown' | 'white' | 'black'
}
type MyDog = LookUp<Cat | Dog, 'dog'> // expected to be `Dog`
This question is relatively simple, as long as you learn to use infer
and extends
:
// 本题答案
type LookUp<T, P> = T extends {
type: infer U
} ? (
U extends P ? T : never
) : never
The judgment of the union type comes one by one, so we only need to write the judgment for each one separately. In the above solution, we first use the type of extend
+ infer
lock T
which contains type
infer U
object, infer U
points to type
, so the ternary operator is used internally to judge U extends P ?
to pick out the type of type
.
The author flipped through the answer and found that there is a more advanced solution:
// 本题答案
type LookUp<U extends { type: any }, T extends U['type']> = U extends { type: T } ? U : never
This solution is more concise and complete:
- Use
extends { type: any }
andextends U['type']
at the generic type to directly lock the input parameter type, so that error checking occurs earlier. -
T extends U['type']
precisely narrows the parameterT
range, what we can learn is that the previously defined genericU
can be used directly by the new generic. -
U extends { type: T }
is a new way of thinking. In the first answer, our way of thinking is "find the objecttype
value for judgment", while the second answer directly uses the entire object structure{ type: T }
judgment, which is more pure TS thinking.
Trim Left
Implement TrimLeft<T>
and clear the left space of the string:
type trimed = TrimLeft<' Hello World '> // expected to be 'Hello World '
To deal with this kind of problem in TS, you can only use recursion, not regularity. It is easier to think of the following way of writing:
// 本题答案
type TrimLeft<T extends string> = T extends ` ${infer R}` ? TrimLeft<R> : T
That is, if the string contains spaces in front of it, remove the spaces and continue the recursion, otherwise return the string itself. The key to mastering this question is that infer
can also be used for derivation within strings.
Trim
Implement Trim<T>
and clear the spaces on the left and right sides of the string:
type trimmed = Trim<' Hello World '> // expected to be 'Hello World'
The simple solution to this problem is to trim both left and right:
// 本题答案
type Trim<T extends string> = TrimLeft<TrimRight<T>>
type TrimLeft<T extends string> = T extends ` ${infer R}` ? TrimLeft<R> : T
type TrimRight<T extends string> = T extends `${infer R} ` ? TrimRight<R> : T
The cost is very low, and the performance is not bad, because it is simple to write TrimLeft
and TrimRight
.
If you don't adopt the practice of "Left first and then Right", if you want to complete it at one time, you must have some TS thinking. The stupid idea is "if there is a space on the left, split the left, or if there is a space on the right, split the right", and finally write a complex ternary expression. A better idea is to use the TS union type:
// 本题答案
type Trim<T extends string> = T extends ` ${infer R}` | `${infer R} ` ? Trim<R> : T
extends
can also be followed by a union type, so that any match will go to Trim<R>
recursion. This is the TS thinking that is more difficult to explain. Without it, you can only think of ternary expressions, but once you understand the union type, you can use it in extends
, TS will help you make N-ary expressions The ability of the style, then the code written will be very elegant.
Capitalize
The implementation Capitalize<T>
capitalizes the first letter of the string:
type capitalized = Capitalize<'hello world'> // expected to be 'Hello world'
If this is a JS question, it would be so simple, but the question is TS, and we need to switch to TS thinking again.
First of all, you need to know how to use the basic function Uppercase
to convert a single letter to uppercase, and then cooperate with infer
Needless to say:
type MyCapitalize<T extends string> = T extends `${infer F}${infer U}` ? `${Uppercase<F>}${U}` : T
Replace
To implement the TS version function Replace<S, From, To>
, replace the string From
with To
:
type replaced = Replace<'types are fun!', 'fun', 'awesome'> // expected to be 'types are awesome!'
把From
夹在字符串中间,前后用两个infer
46fe19a89673dda7eba845804b0db0a3---推导,最后输出时前后不变,把From
To
just do it:
// 本题答案
type Replace<S extends string, From extends string, To extends string,> =
S extends `${infer A}${From}${infer B}` ? `${A}${To}${B}` : S
ReplaceAll
Implement ReplaceAll<S, From, To>
, replace the string From
with To
:
type replaced = ReplaceAll<'t y p e s', ' ', ''> // expected to be 'types'
The difference between this question and the previous question is to replace all. The solution must be recursion. The key is what is the judgment condition for when to recurse. After some thinking, if infer From
can match, does it mean that recursion is possible? So add a layer of ternary judgment From extends ''
to:
// 本题答案
type ReplaceAll<S extends string, From extends string, To extends string> =
S extends `${infer A}${From}${infer B}` ? (
From extends '' ? `${A}${To}${B}` : ReplaceAll<`${A}${To}${B}`, From, To>
) : S
Append Argument
The implementation type AppendArgument<F, E>
, expand the function parameter by one:
type Fn = (a: number, b: string) => number
type Result = AppendArgument<Fn, boolean>
// expected be (a: number, b: string, x: boolean) => number
The question is very simple, just use infer
:
// 本题答案
type AppendArgument<F, E> = F extends (...args: infer T) => infer R ? (...args: [...T, E]) => R : F
Summarize
These questions are relatively simple, mainly to investigate the skilled use of infer
and recursion.
The discussion address is: Intensive Reading "Promise.all, Replace, Type Lookup..." Issue #425 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) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。