您需要类似部分类型参数推断之类的东西,这目前不是 TypeScript 的功能(请参阅microsoft/TypeScript#26242)。现在,您要么必须手动指定所有类型参数,要么让编译器推断所有类型参数;没有部分推断。正如您所注意到的,generic type parameter defaults不要抓挠这个痒;默认值会关闭推理。
所以有一些解决方法。持续工作但使用起来有些烦人的是currying 或“dummying”。此处的柯里化意味着您将单个多类型参数函数拆分为多个单类型参数函数:
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 参数中提供足够的信息,以便编译器通过annotating 或在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}
这可能是我在这里推荐的解决方案,实际上与the other answer posted 相同。因此,所有这些答案所做的都是煞费苦心地解释为什么你想做的事情目前是不可能的,以及为什么可用的解决方法会让你远离它。哦,好吧!
好的,希望这有助于了解情况。祝你好运!
Playground link to code