Typing record of functions from a function signature in Typescript

43 Views Asked by At

I want to type memoized selectors in a record in Typescript.

I defined :

type StateFunction<State> = (state: State) => any;
type AnyFunction = (...args: any[]) => any;

type ExtractReturnType<T extends AnyFunction[]> = {
  [I in keyof T]: ReturnType<T[I]>;
};

type MemoizeTuple<T extends AnyFunction[]> = [
  T,
  (...args: ExtractReturnType<T>) => any
];

SIMPLE CASE

If I want to type a function that takes as a parameter a memoized tuple and returns the resulting selector, I have :

type MkSelector<State> = <T extends StateFunction<State>[], F>(
  memoizeTyple: MemoizeTuple<[...T]> & F
) => F extends [infer _, infer B]
  ? (state: State) => B extends (...args: any[]) => infer R ? R : never
  : never;

Let say that I have a state defined as follow:

type State = {
foo: number;
bar: string;
};

And the selectors for foo and bar :

const selectFoo = (state: State) => state.foo;

const selectBar = (state: State) => state.bar;

Then if I declare my function, according to type MkSelector (I don't care about the implementation, this is not the difficult part), then I have :

declare const mkSelector: MkSelector<State>;

const selector = mkSelector([[selectFoo, selectBar], (foo, bar) => null]);

And Typescript, infers selector as :

const selector: (state: State) => null

And the inner function as : (foo: number, bar: string) => null

Which is the expected result.

My final goal to get the desired result for a record of tuples. Getting help from the re-select implementation, I came up with :

type BuildMkSelectors<State> = <
  T extends { [K in keyof T]: StateFunction<State>[] },
  F
>(
  t: {
    [K in keyof T]: MemoizeTuple<[...T[K]]>;
  } & F
) => {
  [K in keyof F]: F[K] extends [any, infer B]
    ? (state: State) => B extends (...args: any[]) => infer R ? R : never
    : never;
};

When applying this :

declare const buildMkSelectors: BuildMkSelectors<State>;

const selectors = buildMkSelectors({
  sel1: [[selectFoo], (foo) => 42],
  sel2: [[selectBar], (bar) => true],
  sel3: [[selectFoo, selectBar], (foo, bar) => null],
});

Typescript infers selectors as :

 const selectors: {
    sel1: (state: State) => number;
    sel2: (state: State) => boolean;
    sel3: (state: State) => null;
}

And the inner tuples as :

{
    sel1: (foo: number) => number;
    sel2: (bar: string) => boolean;
    sel3: (foo: number, bar: string) => null;

}

Which is exactly what I was looking for.

FINAL GOAL

I want to create a generic type to rewrite BuildMkSelectors in a cleaner way, just using BuildMkSelector.

Something like:

type FOverRecord<F extends (...args: any[]) => any> = {
  // iterate over F
};

So that :

type BuildMkSelectors2<State> = FOverRecord<MkSelector<State>>;

Would be strictly equivalent to BuildMkSelectors.

I am stuck there, is that possible to achieve this?

0

There are 0 best solutions below