typescript - 将函数第一个参数的类型推断到第二个参数的类型中
我一直在尝试最大限度地利用 TypeScript 的推断引擎来开发一个工具,但我很难让它按照我想要的方式工作。
通用工具接收一组“映射器”函数作为第一个参数,以及一个“转换器”函数作为第二个参数。
“mapper”函数的结果将成为“transformer”函数的输入。
我想根据“mappers”解析的数据动态推断“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`,
);
点击这里查看此的 TypeScript 游乐场
我希望 transformer 函数能够动态推断为 (string, number) => string,然而,它被类型化为 (any, any) => string。
有没有办法让我让 TypeScript 做这种类型的推断?
答案#1
我认为这里的根本问题是,你希望通过为 Mappers(我称之为 M)使用通用参数默认值,某种程度上能够让它被推断出来。不幸的是,它只会使用默认值,而不会尝试在任何地方进行推断。所以你正在指定 Data(对我来说是 D)和 Result(是 R),然后得到使用到处都是 any 的默认 M。
解决方法本质上是将 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,因为它用作提示,将 mappers 参数解释为元组类型,而不是数组……你需要这样做,以便 transform 的参数顺序强类型化(而不仅仅是一大堆联合类型)。
请注意,transform 函数作用于与 mappers 中函数的 返回类型 对应的映射元组上。
我们不妨直接实现,而不是在运行时使用 {}:
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"
);
其中 name 和 age 已知分别为 string 和 number 类型。
而我上面给出的实现产生了这个结果:
console.log(p({ name: "Billy", age: 9 }));
// BILLY IS 9.0 YEARS OLD
好的,希望有帮助;祝你好运!
代码链接
