简体   繁体   中英

Infer typescript function arguments

Given the following

const action1 = (arg1: string) => {}
const action2 = (arg1: string, arg2: {a: string, b: number}) => {}
const actions = [action1, action2]
handleActions(actions)

... elsewhere ...

const handleActions = (actions: WhatTypeIsThis[]) => {
  const [action1, action2] = actions;
  action1(/** infer string */)
  action2(/** infer string and object */)
}

How can I define the WhatTypeIsThis type in order for the action args to be inferable inside handleActions ?

Is it possible to define it in such a way that actions can be any number of functions with varying argument lists?

Is it possible using generics?

Is it possible to define it in such a way that actions can be any number of functions with varying argument lists?

With a dynamic list, you need runtime checking. I don't think you can do runtime checking on these functions without branding them (I tried doing it on length since those two functions have different lengths, but it didn't work and it really wouldn't have been useful even if it had — (arg1: string) => void and (arg1: number) => void are different functions with the same length ).

With branding and a runtime check, it's possible:

  • Define branded types for the functions
  • Create the functions
  • Define the action list as an array of a union of the function types
  • Have handleActions branch on the brand

Like this:

type Action1 = (
    (arg1: string) => void
) & {
    __action__: "action1";
};

type Action2 = (
    (arg1: string, arg2: {a: string, b: number}) => void
) & {
    __action__: "action2";
};

const action1: Action1 = Object.assign(
    (arg1: string) => {},
    {__action__: "action1"} as const
);
const action2: Action2 = Object.assign(
    (arg1: string, arg2: {a: string, b: number}) => {},
    {__action__: "action2"} as const
);

const actions = [action1, action2];

type ActionsList = (Action1 | Action2)[];

const handleActions = (actions: ActionsList) => {
    const [action1, action2] = actions;
    if (action1.__action__ === "action1") {
        action1("x");   // <==== infers `(arg: string) => void` here
    }
};  

handleActions(actions);

Playground link


Before you added the text quoted at the top of the answer, it was possible with a readonly tuple type . I'm keeping this in the answer in case it's useful to others, even though it doesn't apply to your situation.

Here's what that looks like:

type ActionsList = readonly [
    (arg1: string) => void,
    (arg1: string, arg2: { a: string; b: number; }) => void
];

To make it readonly, you'll need as const on actions :

const actions = [action1, action2] as const;
//                                ^^^^^^^^^

A tuple is a kind of "...Array type that knows exactly how many elements it contains, and exactly which types it contains at specific positions." (from the link above)

Playground link

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM