【发布时间】:2018-12-04 21:02:34
【问题描述】:
假设我有一个函数,它接受两个函数f 和g 作为参数并返回一个执行f 和g 的函数并返回一个带有结果的对象。我还想强制 f 和 g 具有相同的签名。这对于条件类型来说很容易:
type ArgumentTypes<F extends Function> = F extends (...args: infer A) => any ? A : never;
function functionPair<
F extends (...args: any[]) => any,
G extends (...args: ArgumentTypes<F>) => any
>
(f: F, g: G): (...args: ArgumentTypes<F>) => { f: ReturnType<F>, g: ReturnType<G> }
{
return (...args: ArgumentTypes<F>) => ({ f: f(...args), g: g(...args) });
}
functionPair((foo: string) => foo, (bar: number) => bar); // Error, incompatible signatures, as expected
functionPair((foo: string) => foo, (bar: string) => bar.length); // (foo: string) => { f: string; g: number; }, as expected
现在,如果我想让f 和g 可选,并因此改变返回对象的形状怎么办?也就是说,如果 f 或 g 是 undefined,则结果对象中应该缺少它们的键:
functionPair(); // Should be () => {}
functionPair(undefined, undefined); // Should be () => {}
functionPair((foo: string) => foo); // Should be (foo: string) => { f: string }
functionPair(undefined, (bar: string) => foo.length); // Should be (bar: string) => { g: number }
functionPair((foo: string) => foo, (bar: string) => foo.length); // Should be (foo: string) => { f: string, g: number }, as before
我一直在尝试使用条件类型来实现这一点,但是在有条件地强制执行结果函数的形状时遇到了一些麻烦。到目前为止,这是我所拥有的(严格的空检查已关闭):
function functionPair<
A extends F extends undefined ? G extends undefined ? [] : ArgumentTypes<G> : ArgumentTypes<F>,
F extends (...args: any[]) => any = undefined,
G extends F extends undefined ? (...args: any[]) => any : (...args: ArgumentTypes<F>) => any = undefined
>
(f?: F, g?: G): (...args: A) =>
F extends undefined
? G extends undefined ? {} : { g: ReturnType<G> }
: G extends undefined ? { f: ReturnType<F> } : { f: ReturnType<F>, g: ReturnType<G> }
{ /* implementation... */ }
const a = functionPair(); // () => {}, as expected
const b = functionPair((foo: string) => foo); // (foo: string) => { f: string; }, as expected
const c = functionPair((foo: string) => foo, (bar: number) => bar); // Error, incompatible signatures, as expected
const d = functionPair((foo: string) => foo, (bar: string) => bar.length); // (foo: string) => { f: string; g: number; }, as expected
const e = functionPair(undefined, undefined); // INCORRECT! Expected () => {}, got (...args: unknown[] | []) => {} | { f: any; } | { g: any; } | { f: any; g: any; }
const f = functionPair(undefined, (bar: string) => bar.length); // INCORRECT! Expected (bar: string) => { g: number; } but got (...args: unknown[] | [string]) => { g: number; } | { f: any; g: number; }
顺便说一句,我知道这在技术上可以通过重载实现,如下所示,但我真的很想了解如何在没有它们的情况下做到这一点。
function functionPairOverloaded(): () => {}
function functionPairOverloaded(f: undefined, g: undefined): () => {}
function functionPairOverloaded<F extends (...args: any[]) => any>(f: F): (...args: ArgumentTypes<F>) => { f: ReturnType<F> }
function functionPairOverloaded<G extends (...args: any[]) => any>(f: undefined, g: G): (...args: ArgumentTypes<G>) => { g: ReturnType<G> }
function functionPairOverloaded<F extends (...args: any[]) => any, G extends (...args: ArgumentTypes<F>) => any>(f: F, g: G): (...args: ArgumentTypes<F>) => { f: ReturnType<F>, g: ReturnType<G> }
function functionPairOverloaded<F extends (...args: any[]) => any, G extends (...args: any[]) => any>(f?: F, g?: G) { /* implementation... */ }
【问题讨论】:
-
您需要启用
strictNullCheks,否则当您将F和G显式传递给functionPair时,它们不会被推断为undefined。您还需要在strictNullCheks下进行其他更改才能使代码正常工作,但如果没有此选项,我认为它无法完成(至少在推断undefined时检测any时需要一些类型的技巧)跨度>
标签: javascript typescript generics conditional-types