问题描述
我有一个具有两种泛型类型的函数,In
和 Out
:
I have a function with two generic types, 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`
此代码当前无法编译,因为在可选类型 (In
) 之后不能有必需的泛型类型 (Out
).
This code currently does not compile, because you cannot have a required generic type (Out
) after an optional one (In
).
我如何告诉 Typescript 编译器我想让这个函数的用户做以下三件事之一:
How do I tell the Typescript compiler that I want to let the user of this function do one of three things:
不要指定任何泛型:
createTask(...)
.In
的类型默认为{}
,Out
应该从TaskFunction
的返回值推断出来.
Don't specify any generics:
createTask(...)
. The type ofIn
should default to{}
, andOut
should be inferred from the return value of theTaskFunction
.
仅指定 In
:createTask(...)
.如上,Out
应该是推断出来的.
Specify only In
: createTask<A>(...)
. As above, Out
should be inferred.
同时指定In
和Out
:createTask(...)
.
本质上,我正在寻找一种方法来说明这个泛型是可选的,应该被推断出来".我知道有一个 infer
关键字,但从我找到的有限文档来看,它似乎不支持这个用例.
Essentially I'm looking for a way to say "this generic is optional and should be inferred". I know there's an infer
keyword but from the limited documentation I've found on it, it doesn't seem to support this use case.
我也尝试为 Out
分配一个默认值,但它总是使用该默认值而不是从 TaskFunction
推断.
I've also tried to assign a default value to Out
, but then it always uses that default value instead of inferring from TaskFunction
.
我可以颠倒 In
和 Out
的顺序,但是 Out
总是必须指定,即使它很容易推断,如果用户想指定In
.
I can reverse the order of In
and Out
, but then Out
always has to be specified even though it can easily be inferred, if the user wants to specify In
.
我也不想强迫用户在每次调用函数时都添加默认值 {}
.
I also prefer not to force users to add the default value {}
every single time they call the function.
这完全可能与 Typescript 相关,还是我必须始终要求指定 In
?
Is this at all possible to do with Typescript, or will I have to always require In
to be specified?
推荐答案
您想要像部分类型参数推断这样的东西,它目前不是 TypeScript 的功能(请参阅 microsoft/TypeScript#26242).现在,您要么必须手动指定所有类型参数,要么让编译器推断所有类型参数;没有部分推断.正如您所注意到的,generic类型参数默认值不要抓挠这个痒处;默认设置关闭推理.
You want something like partial type parameter inference, which is not currently a feature of TypeScript (see microsoft/TypeScript#26242). Right now you either have to specify all type parameters manually or let the compiler infer all type parameters; there's no partial inference. As you've noticed, generic type parameter defaults do not scratch this itch; a default turns off inference.
所以有解决方法.那些始终有效但使用起来也有些烦人的方法是 currying 或dummying".在这里柯里化意味着您将单个多类型参数函数拆分为多个单类型参数函数:
So there are workarounds. The ones that work consistently but are also somewhat annoying to use are either currying or "dummying". Currying here means you split the single multi-type-argument function into multiple single-type-argument functions:
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
和 O
类型,你有你想要的确切行为,但有一个烦人的延迟函数调用.
You have the exact behavior you want with respect to your I
and O
types, but there's this annoying deferred function call in the way.
此处的虚拟意味着您为函数提供了一个您想要手动指定的类型的虚拟参数,并让推理代替手动指定:
Dummying here means that you give the function a dummy parameter of the types you'd like to manually specify, and let inference take the place of manual specification:
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}
同样,您拥有想要的行为,但您将无意义/虚拟值传递给函数.
Again, you have the behavior you want, but you are passing in nonsense/dummy values to the function.
当然,如果您已经拥有正确类型的参数,则不需要添加虚拟"参数.在您的情况下,您当然可以在 task
参数中提供足够的信息,以便编译器通过 或以其他方式指定 任务中的类型
参数:
Of course, if you already have parameters of the right types, you shouldn't need to add a "dummy" parameter. In your case, you certainly can provide enough information in the task
parameter for the compiler to infer I
and O
, by annotating or otherwise specifying the types inside your task
parameter:
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}
这可能是我在这里推荐的解决方案,实际上与发布的其他答案相同.因此,所有这些答案都在煞费苦心地解释为什么您想要做的事情目前是不可能的,以及为什么可用的解决方法使您远离它.哦,好吧!
This is probably the solution I'd recommend here, and is in effect the same as the other answer posted. So all this answer is doing is painstakingly explaining why what you want to do isn't currently possible and why the available workarounds lead you away from it. Oh well!
好的,希望这有助于理解情况.祝你好运!
Okay, hope that helps make sense of the situation. Good luck!
这篇关于打字稿:在可选的第一个泛型之后推断泛型的类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!