Typescript 推断执行具有相同参数签名的可选函数参数的高阶函数类型
Typescript infer type of higher-order function that executes optional function parameters with same argument signatures
假设我有一个函数接受两个函数 f
和 g
作为参数,returns 一个函数执行 f
和 g
并且returns 带有结果的对象。我还想强制 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
optional 并具有形状怎么办结果返回的对象发生变化?也就是说,如果 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... */ }
假设您已打开 --strictNullChecks
,我想我会这样做:
type Fun = (...args: any[]) => any;
type FunFrom<F, G> = F extends Fun ? F : G extends Fun ? G : () => {};
type IfFun<F, T> = F extends Fun ? T : never;
type Ret<T> = T extends (...args: any[]) => infer R ? R : never
declare function functionPair<
F extends Fun | undefined = undefined,
G extends ((...args: (F extends Fun ? Parameters<F> : any[])) => any)
| undefined = undefined
>(
f?: F,
g?: G
): (...args: Parameters<FunFrom<F, G>>) => {
[K in IfFun<F, 'f'> | IfFun<G, 'g'>]: K extends 'f' ? Ret<F> : Ret<G>
};
这相当丑陋,但它确实为您提供了您正在寻找的行为:
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); // () => {}, as expected
const f = functionPair(undefined, (bar: string) => bar.length); // (bar: string) => { g: number; }, as expected
我决定只使用两个类型参数 F
和 G
而不是 A
使用 Parameters<FunFrom<F, G>>
。请注意,Parameters
是一个类似于您的 ArgumentTypes
的内置类型函数。
此外,对于 returned 函数的 return 类型,我做了一个有点难看的映射类型。我首先计划做一些类似 IfFun<F, {f: Ret<F>}> & IfFun<G, {g: Ret<G>}>
的事情,这(我相信)更容易理解,但结果类型 {f: X, g: Y}
比交集 {f: X} & {g: Y}
更好。
无论如何,希望对您有所帮助。祝你好运!
如果您希望能够关闭 --strictNullChecks
,那么定义会更加复杂:
type Fun = (...args: any[]) => any;
type AsFun<F> = [F] extends [Fun] ? F : never
type FunFrom<F, G> = AsFun<IfFun<F, F, IfFun<G, G, () => {}>>>;
type IfFun<F, Y, N=never> = F extends undefined ? N :
0 extends (1 & F) ? N : F extends Fun ? Y : N;
type Ret<T> = T extends (...args: any[]) => infer R ? R : never
declare function functionPair<
F extends Fun | undefined = undefined,
G extends ((...args: IfFun<F, Parameters<F>, any[]>) => any)
| undefined = undefined
>(
f?: F,
g?: G
): (...args: Parameters<FunFrom<F, G>>) => {
[K in IfFun<F, 'f'> | IfFun<G, 'g'>]: K extends 'f' ? Ret<F> : Ret<G>
};
不同之处在于IfFun<>
需要能够区分undefined
和any
的功能,当您关闭[=13=时,它们都会在不幸的地方弹出].这是因为 undefined extends Function ? true : false
开始 returning true
,当您将手动 undefined
值传递给函数时,any
开始被推断。区分 undefined
相当简单,因为 Function extends undefined ? true : false
仍然是 false
,但是区分 any
很烦人并且涉及一些 。
再次祝你好运!
假设我有一个函数接受两个函数 f
和 g
作为参数,returns 一个函数执行 f
和 g
并且returns 带有结果的对象。我还想强制 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
optional 并具有形状怎么办结果返回的对象发生变化?也就是说,如果 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... */ }
假设您已打开 --strictNullChecks
,我想我会这样做:
type Fun = (...args: any[]) => any;
type FunFrom<F, G> = F extends Fun ? F : G extends Fun ? G : () => {};
type IfFun<F, T> = F extends Fun ? T : never;
type Ret<T> = T extends (...args: any[]) => infer R ? R : never
declare function functionPair<
F extends Fun | undefined = undefined,
G extends ((...args: (F extends Fun ? Parameters<F> : any[])) => any)
| undefined = undefined
>(
f?: F,
g?: G
): (...args: Parameters<FunFrom<F, G>>) => {
[K in IfFun<F, 'f'> | IfFun<G, 'g'>]: K extends 'f' ? Ret<F> : Ret<G>
};
这相当丑陋,但它确实为您提供了您正在寻找的行为:
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); // () => {}, as expected
const f = functionPair(undefined, (bar: string) => bar.length); // (bar: string) => { g: number; }, as expected
我决定只使用两个类型参数 F
和 G
而不是 A
使用 Parameters<FunFrom<F, G>>
。请注意,Parameters
是一个类似于您的 ArgumentTypes
的内置类型函数。
此外,对于 returned 函数的 return 类型,我做了一个有点难看的映射类型。我首先计划做一些类似 IfFun<F, {f: Ret<F>}> & IfFun<G, {g: Ret<G>}>
的事情,这(我相信)更容易理解,但结果类型 {f: X, g: Y}
比交集 {f: X} & {g: Y}
更好。
无论如何,希望对您有所帮助。祝你好运!
如果您希望能够关闭 --strictNullChecks
,那么定义会更加复杂:
type Fun = (...args: any[]) => any;
type AsFun<F> = [F] extends [Fun] ? F : never
type FunFrom<F, G> = AsFun<IfFun<F, F, IfFun<G, G, () => {}>>>;
type IfFun<F, Y, N=never> = F extends undefined ? N :
0 extends (1 & F) ? N : F extends Fun ? Y : N;
type Ret<T> = T extends (...args: any[]) => infer R ? R : never
declare function functionPair<
F extends Fun | undefined = undefined,
G extends ((...args: IfFun<F, Parameters<F>, any[]>) => any)
| undefined = undefined
>(
f?: F,
g?: G
): (...args: Parameters<FunFrom<F, G>>) => {
[K in IfFun<F, 'f'> | IfFun<G, 'g'>]: K extends 'f' ? Ret<F> : Ret<G>
};
不同之处在于IfFun<>
需要能够区分undefined
和any
的功能,当您关闭[=13=时,它们都会在不幸的地方弹出].这是因为 undefined extends Function ? true : false
开始 returning true
,当您将手动 undefined
值传递给函数时,any
开始被推断。区分 undefined
相当简单,因为 Function extends undefined ? true : false
仍然是 false
,但是区分 any
很烦人并且涉及一些
再次祝你好运!