Topic describes

The original address has a class called EffectModule

class EffectModule {}


Copy the code

This object has a variety of attributes, such as string, number, function, etc., where the function attribute can only have two types of signature:

asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>>
syncMethod<T, U>(action: Action<T>): Action<U>
Copy the code

Now we have a function called connect that takes an instance of EffectModule and turns it into another object that has only the same method as the EffectModule, but whose type signature has been changed:

interfaceAction<T> { payload? : Ttype: string
}

asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>> becomes asyncMethod<T, U>(input: T): Action<U> syncMethod<T, U>(Action: Action<T>): Action<U> becomes syncMethod<T, U>(Action: T): Action<U>Copy the code

Example: EffectModule is defined as follows:

interfaceAction<T> { payload? : T;type: string;
}

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"}; }}Copy the code

After connect:

// The problem is to replace any with solutions to make ts compile properly
Connect returns the same value as Connected. Ts compiles
type Connect = (module: EffectModule) = > any

const connect: Connect = m= > ({
  delay: (input: number) = > ({
    type: 'delay',
    payload: `hello 2`
  }),
  setMessage: (input: Date) = > ({
    type: "set-message",
    payload: input.getMilliseconds()
  })
});

type Connected = {
  delay(input: number): Action<string>
  setMessage(action: Date): Action<number>}const effectModule = new EffectModule()
const connected: Connected = connect(effectModule)
Copy the code

Type Connect = (module: EffectModule) => any, replace any with solution, let Connect return the same value as Connected, and let ts compile properly

Obviously, what we need to do is:

  • Extract the function type of the EffectModule
  • Solve the promise/action for the function’s arguments and return values

Extract the function type of the EffectModule

And speaking of anything, it’s a Pick or Omit Omit operation. However, ts does not have the same method as object.keys ().filter, which requires the mapping type +never to do special processing. The whole process is: mapping type = if the value is a function type, return key, otherwise never =, evaluate the mapping type, and get the key of the function type

Mapping type

Key is used to map one type to another. Key uses a syntax similar to for in to iterate over each key in the old type:

type mapType0<T> = {
  [k in keyof T]: T[k]
}
Copy the code

Add [keyof T] to the end of the mapping type, which is equivalent to the method valueof.

const o = {
    a: 1,
    b: '2'
}

type map1 = mapType0<typeof o>[keyof typeof o]
// string | number
Copy the code

Pick and Omit

Now that we know the mapping type, we can implement a Pick based on this (ts already comes with it)

type myPick<T, K extends keyof T> = {
  [k in K]: T[k]
}
Copy the code

The second generic parameter constraint key comes from T, which ensures that some of the original object’s keys are retrieved. 8. Omit anything. 8

type MyOmit<T, K extends keyof any> 
= Pick<T, Exclude<keyof T, K>>;
Copy the code

Get the key whose value is of type function

type FunctionKeys<T> = {
  [k in keyof T]: T[k] extends Function ? k : never
}[keyof T]
type functionKeys = Pick<EffectModule.FunctionKeys<EffectModule>>
Copy the code

Use Omit Omit

type noFunctionKeys<T> = {
  [k in keyof T]: T[k] extends Function ? never : k
}[keyof T]
type functionKeys = Omit<EffectModule.noFunctionKeys<EffectModule>>
Copy the code

Now, you have an object with only a key of type value

Solve the promise/action for the function’s arguments and return values

infer

Infer refers to type variables to be inferred in conditional statements of condition type, which can be understood as solving equations. Infer X means x is the variable to be solved. Infer is equivalent to a marking function. For example, ReturnType is achieved by infer

type MyReturnType<T> = T extends(... args:any[]) => infer P ? P : any;
Copy the code

Let’s look at two more examples, such as resolving promises and getting the item type of the array:

type UnPromisify<T> = T extends Promise<infer U> ? U : T
type UnArray<T> = T extends (infer U)[] ? U : T
Copy the code

Implementation effect

So, based on the previous steps, we can extract function parameters, return values, and solve promises and actions

type UnPromisify<T> = T extends Promise<infer U> ? U : T
type UnAction<T> = T extends Action<infer U> ? U : T

type MapTypeToUnPromisifyAndUnAction<T extends any[]> = {
  [k in keyof T]: UnAction<UnPromisify<T[k]>>
}

type Connect = (module: EffectModule) = >({[functionKey in keyof functionKeys] : (input: MapTypeToUnPromisifyAndUnAction<
    Parameters<functionKeys[functionKey]>
    >[number]) = >UnPromisify<ReturnType<functionKeys[functionKey] > >})Copy the code
All the code

type FunctionKeys<T> = { [k in keyof T]: T[k] extends Function ? k : never }[keyof T]
type functionKeys = Pick<EffectModule.FunctionKeys<EffectModule>>

type UnPromisify<T> = T extends Promise<infer U>?U : T
type UnAction<T> = T extends Action<infer U>?U : T

type MapTypeToUnPromisifyAndUnAction<T extends any[] > ={
  [k in keyof T]: UnAction<UnPromisify<T[k]>>
}

type Connect = (module: EffectModule) = >({[functionKey in keyof functionKeys] : (input: MapTypeToUnPromisifyAndUnAction<
    Parameters<functionKeys[functionKey]>
    >[number]) = >UnPromisify<ReturnType<functionKeys[functionKey] > >})Copy the code