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?