根据对象键推断函数的参数类型

时间:2020-10-24 19:33:49

标签: typescript

使用下面的代码,我试图根据fn的值来推断args的参数类型。

我需要将good推论为布尔值,将num推论为数字,将bad推论为错误。

现在它们的类型均为string | number | boolean,并且没有错误。

type WithArgs<
  Args extends {
    [key: string]: { value: [string, string | number | boolean] };
  } = {
    [key: string]: { value: [string, string | number | boolean] };
  },
  Arg extends keyof Args = keyof Args
> = {
  args: Args;
  fn: (params: { [key in Arg]: Args[Arg]["value"][1] }) => void;
};

const configs: WithArgs[] = [{
  args: { good: { value: ["bool", true] } },
  fn: ({ good, bad }) => {}
}, {
  args: { num: { value: ["number", 5] } },
  fn: ({ num }) => {}
}];

1 个答案:

答案 0 :(得分:0)

Typescript只能推断函数的类型。由于您尝试键入非函数,因此泛型类型必须包含一个参数。

如果我为该类型提供第一个参数,则good只能为true,并且我们会收到错误消息,指出“ TS2339:类型'{不存在属性'bad','good:true ;}'。”

const configs: WithArgs<{ good: { value: ['bool', true] } }>[] = [{
  args: { good: { value: ["bool", true] } },
  fn: ({ good, bad }) => {}
}];

当然,我不确定您为什么要这么做。我很想知道?。

编辑(playground link):

我接受了自己的建议。知道Typescript 可以从函数中推断类型,那么我们要做的就是创建一个函数。

首先,我为Args添加了一个定义,并更新了WithArgs类型:

type Args<T> = {
    [key: string]: { value: [string, T] };
};

type WithArgs<
    A extends Args<unknown> = {
        [key: string]: { value: [string, string | number | boolean] };
    },
    Arg extends keyof A = keyof A
    > = {
    args: A;
    fn: (params: { [key in Arg]: A[Arg]["value"][1] }) => void;
};

然后,我定义一个函数来推断单个配置的类型:

const withArgs = <C, A extends Args<C>, F extends keyof A>(config: WithArgs<A, F>) => config;

现在,我们根据需要收到bad的错误:

const invalid = withArgs({
    args: { good: { value: ["bool", true] } },
    fn: ({ good, bad }) => {} // Property 'bad' does not exist on type '{ good: boolean; }'
});

当然,您希望能够定义这些配置的数组。因此,我们可以定义一个可以正确键入数组的函数:

type GetWithArgs<T> = T extends WithArgs<infer A, infer F> ? WithArgs<A, F> : never;

const withArgsArr = <T extends WithArgs<Args<unknown>, string | number>[]>(...configs: T) => configs.map(withArgs) as { [i in keyof T]: GetWithArgs<T[i]> };

现在,我还没有弄清楚这一点,但是当您以这种方式声明整个配置时,似乎并没有引发错误。类型错误仅在您随后引用特定配置时发生。我希望有人可以帮助您澄清这种行为(编辑:I've now asked the question here)。

const shouldAlsoBeInvalid = withArgsArr(
    {
        args: { good: { value: ["bool", true] } },
        fn: ({ good, bad }) => {}
    }, {
        args: { num: { value: ["number", 5] } },
        fn: ({ num }) => {}
    }
); // no errors yet :(

const requiresBool = shouldAlsoBeInvalid[0].fn({ good: 'string' }); // TS2322: Type 'string' is not assignable to type 'boolean'.
const badDoesNotExist = shouldAlsoBeInvalid[0].fn({ bad: 'string' }); // TS2345: Argument of type '{ bad: string; }' is not assignable to parameter of type '{ good: boolean; }'.
const requiresNumberParameter = shouldAlsoBeInvalid[1].fn({ num: '1' }); // TS2322: Type 'string' is not assignable to type 'number'.