Typescript:在可选的第一个泛型之后推断出泛型的类型 [英] Typescript: infer type of generic after optional first generic

查看:61
本文介绍了Typescript:在可选的第一个泛型之后推断出泛型的类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个具有两种通用类型的函数, 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:

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

  1. Don't specify any generics: createTask(...). The type of In should default to {}, and Out should be inferred from the return value of the TaskFunction.

仅在 In 中指定: createTask< A>(...).如上所述,应该推断出 Out .

Specify only In: createTask<A>(...). As above, Out should be inferred.

同时指定 In Out : createTask< A,B>(...).

本质上,我正在寻找一种方式来表达此泛型是可选的,应该进行推断".我知道有一个 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的功能(请参阅通用类型参数默认值不会消除这种痕迹;默认情况下会关闭 推断.

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!

操场上的代码链接

这篇关于Typescript:在可选的第一个泛型之后推断出泛型的类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆