简体   繁体   English

我可以从输入参数推断类型吗?

[英]Can I infer types from input arguments?

Can types be inferred from an options object?可以从选项对象推断类型吗?

Here is my contrived example.这是我人为的例子。

The idea here is I have a function that takes in an object, where fields are conversion functions (convert A to B, etc).这里的想法是我有一个接受对象的函数,其中字段是转换函数(将 A 转换为 B 等)。 The return of this function is an object with similar functions, but they have a slightly different return type.这个函数的返回是一个功能相似的对象,只是返回类型略有不同。

I've put some generics in but I'm not really sure how to make it work.我已经放入了一些泛型,但我不确定如何使其工作。 I've seen a similar design with Redux Toolkit's createSlice() function, but it's pretty complicated.我在 Redux Toolkit 的createSlice()函数中看到了类似的设计,但它非常复杂。

type InputActionFn = (arg: any) => any;
interface Options {
    actions: {
        [K: string]: InputActionFn;
    };
}

interface ActionPayload<B> {
    payload: B;
}
type OutputActionFn<A, B> = (arg: A) => ActionPayload<B>;

interface ConverterReturn {
    actions: {
        [K: string]: OutputActionFn<any, any>;
    };
}

export function createConvertor(options: Options): ConverterReturn {
    const actions: Record<string, OutputActionFn<any, any>> = {};

    // For each key I create a function that returns my converted payload.
    Object.keys(options.actions).forEach(key => {
        actions[key] = arg => {
            return {
                payload: options.actions[key](arg),
            };
        };
    });

    return {
        actions,
    };
}

And this is how I plan on using it:这就是我计划使用它的方式:

// I can specify as many converter functions here as I want
const conv = createConvertor({
    actions: {
        actionA: (a: number) => a.toFixed(),
        actionB: (b: string) => parseInt(b, 10),
    },
});

// actionA should be typed properly like this:
//   actionA(arg: number) => ActionPayload<string>
const resultA = conv.actions.actionA(5);
console.log(resultA.payload);
// Outputs: "5"

const resultB = conv.actions.actionB('10');
console.log(resultB.payload);
// Outputs: 10

It is possible using typescript to infer the types可以使用打字稿来推断类型

Here's how:就是这样:

type InputActionFn = (arg: any) => any;

interface Options {
  [K: string]: InputActionFn;
}

interface ActionPayload<B> {
  payload: B;
}

type OutputActionFn<A, B> = (arg: A) => ActionPayload<B>;

type Actions<T extends Options> = {
  [key in keyof T]: OutputActionFn<Parameters<T[key]>[0], ReturnType<T[key]>>;
};

export function createConvertor<T extends Options = any>(options: T) {
  const actions = {} as Actions<T>;

  // For each key I create a function that returns my converted payload.
  Object.keys(options).forEach((key) => {
    actions[key] = (arg: Parameters<T[keyof T]>[0]) => {
      return {
        payload: options[key](arg)
      };
    };
  });

  return {
    actions
  };
}

const conv = createConvertor({
  actionA: (a: number) => a.toFixed(),
  actionB: (b: string) => parseInt(b, 10)
});

// Action A is hinted as (arg: number) => ActionPayload<string>
const resultA = conv.actions.actionA(5);
console.log(resultA.payload);
// Outputs: "5"

// Action B is hinted as (arg: string) => ActionPayload<number>
const resultB = conv.actions.actionB('10');
console.log(resultB.payload);
// Outputs: 10

The import part is here导入部分在这里

type Actions<T extends Options> = {
  [key in keyof T]: OutputActionFn<Parameters<T[key]>[0], ReturnType<T[key]>>;
};

We're telling TypeScript to type Actions as an object where it's keys are in the keyof of the generic T. For each of those keys we specify that the property type is an OutputActionFn and pass the corresponding types to the Generics.我们告诉 TypeScript 将 Actions 类型为一个对象,它的键在泛型 T 的 keyof 中。对于每个键,我们指定属性类型是 OutputActionFn 并将相应的类型传递给泛型。 The Parameters<T> Utility Type helps us infer the argument type of the input function you pass (see https://www.typescriptlang.org/docs/handbook/utility-types.html#parameterstype ). Parameters<T>实用程序类型帮助我们推断您传递的输入函数的参数类型(请参阅https://www.typescriptlang.org/docs/handbook/utility-types.html#parameterstype )。 As there's only one expected argument, we pick Parameters<T>[0] as our argument type.由于只有一个预期参数,我们选择Parameters<T>[0]作为我们的参数类型。 Then we use another Utility Type ReturnType<T> (see https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype ) which infers the return type of the input function we pass.然后我们使用另一个实用程序类型ReturnType<T> (参见https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype ),它推断我们传递的输入函数的返回类型。

Only problem will be that ESLint will complain about this.唯一的问题是 ESLint 会抱怨这个。 Might have to ts-ignore that.可能不得不忽略这一点。

// TS2536: Type 'string' cannot be used to index type 'Actions '.
actions[key] = (arg: Parameters<T[keyof T]>[0]) => {
      return {
        payload: options[key](arg)
      };
    };

Edit: I almost forgot.编辑:我差点忘了。 This is of course also a very important detail that should not be overlooked.这当然也是一个非常重要的细节,不容忽视。

export function createConvertor<T extends Options = any>(options: T) {

The generic Type passed to the createConvertor function is applied on the options argument of the function.传递给 createConvertor 函数的泛型 Type 应用于函数的 options 参数。 We do not use the Options interface, which would not return the expected types.我们使用 Options 接口,它不会返回预期的类型。 Though options is still typed as an argument of type Options as we extend the generic type T from Options.尽管 options 仍然被输入为类型 Options 的参数,因为我们从 Options 扩展了泛型类型 T。

Using a couple of TypeScript generic utilities ( in , Parameters , ReturnType , etc.), you can transform object types pretty flexibly.使用几个 TypeScript 通用实用程序( inParametersReturnType等),您可以非常灵活地转换对象类型。

Here's a working example for your code sample:这是您的代码示例的工作示例:

type Payloader<T extends Record<string, any>> = {
  [Key in keyof T]: (...params: Parameters<T[Key]>) => ({
    payload: ReturnType<T[Key]>
  })
}

function createConvertor<Actions extends Record<string, any>>(options: { actions: Actions }) {
  const actions: Payloader<Actions> = {} as any;

  Object.keys(options.actions).forEach((key) => 
      // @ts-ignore
      actions[key] = ((...args) => ({
        payload: options.actions[key](...args),
      })
  ))

  return { actions }
}

const conv = createConvertor({
  actions: {
    actionA: (a: number) => a.toFixed(),
    actionB: (b: string) => parseInt(b, 10)
  },
})

const resultA = conv.actions.actionA(5);
console.log(resultA.payload);
// Outputs: "5"

const resultB = conv.actions.actionB('10');
console.log(resultB.payload);
// Outputs: 10

* Note: The function implementation uses @ts-ignore , assuming we don't need the implementation to be typed correctly. * 注意:函数实现使用@ts-ignore ,假设我们不需要正确键入实现。

TypeScript Playground 打字稿游乐场

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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