Typescript:在可选的第一个泛型之后推断泛型的类型

2023-11-22

我有一个具有两种泛型类型的函数,In and Out:

function createTask<
  In extends Record<string, any> = {},
  Out extends Record<string, any>,
>(task : TaskFunction<In, Out>) : Task<In, Out>

type TaskFunction<In, Out> = (args : TaskWrapper<In>) => Out | Promise<Out>; 
// TaskWrapper wraps several other types and interfaces, so args is more than just `In`

此代码当前无法编译,因为您无法拥有所需的泛型类型(Out) 在可选的一个 (In).

如何告诉 Typescript 编译器我想让此函数的用户执行以下三件事之一:

  1. 不要指定任何泛型:createTask(...)。的类型In应默认为{}, and Out应该从返回值推断TaskFunction.

  2. 仅指定In: createTask<A>(...)。如上,Out应该可以推断。

  3. 指定两者In and Out: createTask<A, B>(...).

本质上,我正在寻找一种方法来表达“这个泛型是可选的,应该被推断”。我知道有一个infer关键字,但从我找到的有限文档来看,它似乎不支持这个用例。

我还尝试分配默认值Out,但它总是使用该默认值而不是从中推断TaskFunction.

我可以颠倒顺序In and Out, 但是之后Out如果用户想要指定,即使可以很容易地推断出,也始终必须指定In.

我也不想强迫用户添加默认值{}每次他们调用该函数时。

这是否可能与 Typescript 有关,或者我必须始终要求In要具体说明吗?


您需要类似部分类型参数推断之类的功能,这目前不是 TypeScript 的功能(请参阅微软/TypeScript#26242)。现在,您要么必须手动指定所有类型参数,要么让编译器推断所有类型参数;没有片面的推论。正如你所注意到的,泛型类型参数默认值 do not挠痒痒;默认转off推理。

所以有解决方法。那些可以持续工作但使用起来也有些烦人的要么是currying或“假装”。这里的柯里化意味着将单个多类型参数函数拆分为多个单类型参数函数:

type Obj = Record<string, any>; // save keystrokes later

declare const createTaskCurry:
    <I extends Obj = {}>() => <O extends Obj>(t: TaskFunction<I, O>) => Task<I, O>;

createTaskCurry()(a => ({ foo: "" }));
// I is {}, O is {foo: string}
createTaskCurry<{ bar: number }>()(a => ({ foo: "" }));
// I is {bar: number}, O is {foo: string}
createTaskCurry<{ bar: number }>()<{ foo: string, baz?: number }>(a => ({ foo: "" }));
// I is {bar: number}, O is {foo: string, baz?: number}

对于你的行为,你有你想要的确切行为I and O类型,但是有一个令人讨厌的延迟函数调用。


这里的虚拟意味着你给函数一个你想要手动指定的类型的虚拟参数,并让推理代替手动指定:

declare const createTaskDummy:
    <O extends Obj, I extends Obj = {}>(t: TaskFunction<I, O & {}>, 
      i?: I, o?: O) => Task<I, O>;

createTaskDummy(a => ({ foo: "" }));
// I is {}, O is {foo: string}
createTaskDummy(a => ({ foo: "" }), null! as { bar: number });
// I is {bar: number}, O is {foo: string}
createTaskDummy(a => ({ foo: "" }), null! as { bar: number }, 
  null! as { foo: string, baz?: number });
// I is {bar: number}, O is {foo: string, baz?: number}

同样,您拥有所需的行为,但您向函数传递了无意义/虚拟值。

当然,如果您已经拥有正确类型的参数,则不需要添加“虚拟”参数。就您而言,您当然可以在中提供足够的信息task编译器推断的参数I and O, by 注释或以其他方式指定您的内部类型task范围:

declare const createTaskAnnotate: 
  <O extends Obj, I extends Obj = {}>(t: TaskFunction<I, O>) => Task<I, O>;

createTaskAnnotate(a => ({ foo: "" }));
// I is {}, O is {foo: string}
createTaskAnnotate((a: { bar: number }) => ({ foo: "" }));
// I is {bar: number}, O is {foo: string}
createTaskAnnotate((a: { bar: number }): { foo: string, baz?: number } => ({ foo: "" }));
// I is {bar: number}, O is {foo: string, baz?: number}

这可能是我在这里推荐的解决方案,实际上与另一个答案已发布。因此,所有这个答案所做的都是煞费苦心地解释为什么你想做的事情目前是不可能的,以及为什么可用的解决方法会导致你远离它。那好吧!


好的,希望这有助于理解这种情况。祝你好运!

Playground 代码链接

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Typescript:在可选的第一个泛型之后推断泛型的类型 的相关文章

随机推荐