将函数的第一个arg的类型结果推断为第二个arg的类型

时间:2019-07-16 21:59:08

标签: typescript typescript-generics

我一直在尝试将Typescript的推理引擎最大程度地发挥我正在开发的实用程序的作用,但是,我一直在努力使其能够按需运行。

通用util接收一组“映射器”功能作为第一个参数,并接收“ transformer”功能作为第二个参数。

“映射器”功能的结果将成为“变压器”功能的输入。

我想根据“映射器”解析的数据将参数类型动态地推导到“ transformer”函数。

我已经创建了以下基本说明:

type Transformation<
  Data extends object = {},
  Result = any,
  Mappers extends Array<(data: Data) => any> = Array<(data: Data) => any>
> = (
  mappers: Mappers,
  transform: (
    // Over here I am trying to infer out the results of my mapper, with this
    // implementation supporting up to 2 mappers
    ...args: Mappers extends [(data: Data) => infer Arg1]
      ? [Arg1]
      : Mappers extends [(data: Data) => infer Arg1, (data: Data) => infer Arg2]
      ? [Arg1, Arg2]
      : any[]
  ) => Result,
) => void;

interface Person {
  name: string;
  age: number;
}

const personTransformer = {} as Transformation<Person, string>;

personTransformer(
  // mappers:
  [person => person.name, person => person.age],
  //                 |                      |
  // |---------------|                      |
  // |                                      |
  // |    |---------------------------------- 
  // |    |
  (name, age) => `${name} is ${age} years old`,
);

Click here for the TypeScript playground of this

我希望将转换器函数动态地推断为(string, number) => string,但是将其键入为(any, any) => string

有什么办法可以让TypeScript进行这种推断?

1 个答案:

答案 0 :(得分:0)

我认为这里的根本问题是,您希望通过为Mappers使用generic parameter default(我将其称为M),以某种方式来推断它。不幸的是,它将仅使用默认值,而不是尝试在任何地方进行推断。因此,您要指定Data(对我来说是D)和Result(对我来说就是R),并获得使用所有M的默认any在这个地方。

本质上,此修复程序是使Transformation<D, R>本身成为依赖于类型M的泛型函数。因此,您拥有type Transformation<D, R, M> = ()=>{}而不是type Transformation<D, R> = <M>()=>{}。这样就可以从M的使用中推断出它。 (就我所知,您应该将其设置为type Transformation = <D, R, M>()=>{},并由Transformation的调用者指定所有三个通用参数,但这与您的用例有关。)

这就是我要对其进行定义的方式:

// less restrictive form of ReturnType<T>
type Ret<T> = T extends (...args: any) => infer R ? R : never;

type Transformation<D, R> = <M extends ReadonlyArray<(data: D) => any>>(
  mappers: M | [], // | [] hints that mappers should be a tuple type
  transform: (...args: { [K in keyof M]: Ret<M[K]> }) => R
) => (data: D) => R; // I assume you want to actually transform things

如果您希望(data: D) => R像您的版本一样工作,则可以将其替换为void。我只是认为Transformation<D, R>应该产生实际上将D转换为R的东西才有意义。

使用M | []代替M,因为它充当hint来将mappers参数解释为元组类型而不是数组...并且您需要这样做,以便严格按顺序键入transform的参数(而不仅仅是一大堆东西)。

请注意,transform函数在mappers中对应于函数返回类型mapped tuple上进行操作。

我们也可以在运行时而不是使用{}来实现:

const transformer = <D, R>() =>
  (((mappers: ((d: D) => any)[], transform: (...a: any[]) => R) => (data: D) =>
    transform(...mappers.map(m => m(data)))) as any) as Transformation<D, R>;

这必须使用一些类型断言,但是它基本上将mappers中的每个函数应用于data,结果数组散布到transform()中。

最后,我们可以使用上面的内容获得Transformation<Person, string>

const personTransformer = transformer<Person, string>();

我们看到它的行为与您期望的一样

const p = personTransformer(
  [person => person.name, person => person.age],
  (name, age) => name.toUpperCase() + " IS " + age.toFixed(1) + " YEARS OLD"
);

已知nameage分别是stringnumber类型的地方。

我在上面给出的实现结果如下:

console.log(p({ name: "Billy", age: 9 }));
// BILLY IS 9.0 YEARS OLD

好的,希望能有所帮助;祝你好运!

Link to code