Home > Software design >  Tuple argument type inference in Typescript
Tuple argument type inference in Typescript

Time:05-06

Is it possible to achieve the folowing in Typescript:

I want the type of state.initial to be deduced as a tuple based on the input to the takeConfig. It would also be nice to deduce the return type of initialState functions based on the type of state property of component's argument (Although the latter I believe is not possible without the alteration of this API).

type State1 = { a: string };
type State2 = { b: string };

function Component1(props: { state: State1 }) {}
function Component2(props: { state: State2 }) {}

const state = takeConfig([
    {
        component: Component1,
        initialState: () => ({
            a: "a"
        }) // return type here is deduced to be State1
    },
    {
        component: Component2,
        initialState: () => ({
            b: "b"
        }) // return type here is deduced to be State2
    },
]);


// type here is deduced to be [State1, State2]
state.initial

Thanks!

CodePudding user response:

If your question is "is it possible" and not "how to do it" then the answer is yes.

Let's first define the type of an object that your takeConfig function would take:

type Conf<C extends (...args: any[]) => any> = {
    component: C;
    initialState: () => Parameters<C>[0]["state"];
};

Then we'll make a type to infer all the component function types in the tuple:

type InferConfs<C extends ReadonlyArray<unknown>> = {
    [K in keyof C]: C[K] extends Conf<infer Comp> ? Conf<Comp> : C[K];
};

Next we'll need a type to give us state.initial in a similar manner to InferConfs:

type JustStates<C extends ReadonlyArray<unknown>> = {
    [K in keyof C]: C[K] extends Conf<infer _> ? ReturnType<C[K]["initialState"]> : C[K];
};

Finally let's create a type to hold JustStates:

type ReturnedState<Confs extends ReadonlyArray<unknown>> = {
    initial: JustStates<Confs>;
};

And also a type to remove the readonly that as const will give:

type MakeNotReadonly<T> = {
    -readonly [K in keyof T]: T[K];
};

Now we put all of these together to define our takeConfig function:

function takeConfig<C extends ReadonlyArray<unknown>>(conf: InferConfs<C>): ReturnedState<MakeNotReadonly<InferConfs<C>>> { ... }

You must implement the function yourself, however. I am just helping with the types.

The basic principle is that TypeScript can infer the general type that we pass to takeConfig, and we use InferConfs to further narrow the types of the configs. Then in the return type we infer the configs yet again, then do some type transformations (JustState) on it and return it.

Playground

  • Related