[英]Typescript: infer type of generic after optional first generic
我有一个 function 有两种通用类型, In
和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
)。
我如何告诉 Typescript 编译器我想让这个 function 的用户做以下三件事之一:
不要指定任何 generics: createTask(...)
。 In
的类型应该默认为{}
,并且Out
应该从TaskFunction
的返回值中推断出来。
仅指定In
: createTask<A>(...)
。 如上所述,应该推断出Out
。
同时指定In
和Out
: createTask<A, B>(...)
。
本质上,我正在寻找一种方式来表示“这个泛型是可选的,应该被推断出来”。 我知道有一个infer
关键字,但从我找到的有限文档来看,它似乎不支持这个用例。
我也尝试为Out
分配一个默认值,但它总是使用该默认值而不是从TaskFunction
推断。
我可以颠倒In
和Out
的顺序,但是如果用户想要指定In
,那么即使可以很容易地推断出来,也必须指定Out
。
我也不想强迫用户每次调用 function 时都添加默认值{}
。
这是否与 Typescript 有关系,还是我必须始终要求指定In
?
您想要部分类型参数推断之类的东西,这目前不是 TypeScript 的功能(请参阅microsoft/TypeScript#26242 )。 现在,您要么必须手动指定所有类型参数,要么让编译器推断所有类型参数; 没有部分推断。 正如您所注意到的, 泛型类型参数的默认值并没有解决这个问题。 默认关闭推理。
所以有解决方法。 那些始终如一但使用起来也有些烦人的是currying或“dummy”。 此处的柯里化意味着您将单个多类型参数函数拆分为多个单类型参数函数:
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
类型,您有您想要的确切行为,但是有这个烦人的延迟函数调用。
这里的虚拟化意味着你给函数一个你想要手动指定的类型的虚拟参数,并让推理代替手动指定:
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
参数中提供足够的信息,以便编译器通过注释或以其他方式指定task
参数中的类型来推断I
和O
:
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}
这可能是我在这里推荐的解决方案,实际上与发布的其他答案相同。 因此,所有这些答案所做的都是煞费苦心地解释为什么你想做的事情目前是不可能的,以及为什么可用的解决方法会让你远离它。 那好吧!
好的,希望这有助于理解这种情况。 祝你好运!
首先,完全去掉默认类型:
declare function createTask<
In extends Record<string, any>,
Out extends Record<string, any>,
>(task: TaskFunction<In, Out>): Task<In, Out>;
对于您所描述的情况,其中In
被传递:
const r1 = createTask<{ a : number }>(arg => {
return { b: arg.a };
}); // Error: Expected 2 type arguments, but got 1.
不要将其作为类型参数传递。 注释您要约束的值并让它推断其余类型:
const r1 = createTask((arg: { a: number }) => {
return { b: arg.a };
}); // r1 is Task<{a: number;}, {b: number;}>
当所有类型都已知时,这也有效:
declare function foo(arg: { a: number }): { b: boolean };
const r1 = createTask(foo); // r1 is Task<{a: number;}, { b: boolean;}>
正如您在编辑中指出的那样,我尝试添加TaskWrapper
。 解决方案似乎相同。
type Task<In, Out> = { a: In, b: Out}
type TaskWrapper<T> = { a: T }
type TaskFunction<In, Out> = (args : TaskWrapper<In>) => Out | Promise<Out>;
declare function createTask<
In extends Record<string, any>,
Out extends Record<string, any>,
>(task : TaskFunction<In, Out>) : Task<In, Out>
const r1 = createTask((args: TaskWrapper<{ a: number }>) => {
return { b: args.a.a };
}); // r1 is Task<{a: number;}, {b: number;}>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.