TypeScript design of tool type
Article directory
Preparatory knowledge
- TypeScript advanced type Partial
- TypeScript advanced type - condition type (important pre knowledge)
- TypeScript advanced types - practical tips
Try to solve an interview question
The detailed description can be seen in the above link, and only the core points are described here
We have a class of EffectModule. The methods in this class can only have two types of signatures. In addition, there may be some arbitrary non function properties in this class, as follows
interface Action<T> { payload?: T; type: string; } // The signature corresponds to delay asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>> // The signature corresponds to setMessage syncMethod<T, U>(action: Action<T>): Action<U> class EffectModule { count = 1; message = "hello!"; delay(input: Promise<number>) { return input.then(i => ({ payload: `hello ${i}!`, type: 'delay' }); } setMessage(action: Action<Date>) { return { payload: action.payload!.getMilliseconds(), type: "set-message" }; } }
Now there is a function called connect, which accepts the instance of EffectModule and turns it into another object. On this object, there is only the method with the same name of EffectModule, but the type signature of the method has been changed, as follows:
asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>> //Turned into asyncMethod<T, U>(input: T): Action<U> syncMethod<T, U>(action: Action<T>): Action<U> //Turned into syncMethod<T, U>(action: T): Action<U>
The result after connect is:
// Method name, function signature changed type Connected = { delay(input: number): Action<string> setMessage(action: Date): Action<number> } const effectModule = new EffectModule() const connected: Connected = connect(effectModule)
The core of this problem is to deduce the Connected type from the EffectModule type.
First, let's analyze the solution steps:
- Find function name in EffectModule
- Transform function signature
- Combining branches to form trunk
The first step is to design a type tool to get the function property name in the object type
// The first two in the preliminary knowledge have been described in detail, which will not be explained here type FunctionKeys<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];
In the second step, write out the method types before and after the conversion
type asyncMethod<T, U> = (input: Promise<T>) => Promise<Action<U>> // Before conversion type asyncMethodConnect<T, U> = (input: T) => Action<U> // After conversion type syncMethod<T, U> = (action: Action<T>) => Action<U> // Before conversion type syncMethodConnect<T, U> = (action: T) => Action<U> // After conversion
So how to transform it? We need to use condition type and inference type
// T is the signature of the method in the EffectModule type EffectModuleMethodsConnect<T> = T extends asyncMethod<infer U, infer V> ? asyncMethodConnect<U, V> : T extends syncMethod<infer U, infer V> ? syncMethodConnect<U, V> : never;
Finally, the following results are obtained:
type Connect = (module: EffectModule) => { [M in FunctionKeys<EffectModule>]: EffectModuleMethodsConnect<EffectModule[M]> }
Tool type analysis and design considerations
- Master the core knowledge points (generics, index query operators, index accessors, mapping types, conditional types, distributed conditional types, conditional type inference, all of which are included in the preparatory knowledge and can be viewed selectively)
- Understand the meaning of the question, make clear the corresponding relationship between "input" and "output", and figure out what we need to do
- Disassemble steps, try to combine target results, and deduce unknown results according to known conditions
- Try to optimize and merge the steps to find the optimal solution
How to write training tools?
Pay attention to basic knowledge and do more exercises.
Here we give a tool type code base, you can try to read it, and try to write it when you understand it.