【问题标题】:Typescript infer type of higher-order function that executes optional function parameters with same argument signaturesTypescript 推断执行具有相同参数签名的可选函数参数的高阶函数的类型
【发布时间】:2018-12-04 21:02:34
【问题描述】:

假设我有一个函数,它接受两个函数fg 作为参数并返回一个执行fg 的函数并返回一个带有结果的对象。我还想强制 fg 具有相同的签名。这对于条件类型来说很容易:

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

现在,如果我想让fg 可选,并因此改变返回对象的形状怎么办?也就是说,如果 fgundefined,则结果对象中应该缺少它们的键:

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,否则当您将FG 显式传递给functionPair 时,它们不会被推断为undefined。您还需要在strictNullCheks 下进行其他更改才能使代码正常工作,但如果没有此选项,我认为它无法完成(至少在推断undefined 时检测any 时需要一些类型的技巧)跨度>

标签: javascript typescript generics conditional-types


【解决方案1】:

假设你打开了--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

我决定只使用两个类型参数FG,而不是A 使用Parameters&lt;FunFrom&lt;F, G&gt;&gt;。请注意,Parameters 是一个内置类型函数,类似于您的ArgumentTypes

另外,对于返回函数的返回类型,我做了一个有点难看的映射类型。我最初计划做类似IfFun&lt;F, {f: Ret&lt;F&gt;}&gt; &amp; IfFun&lt;G, {g: Ret&lt;G&gt;}&gt; 的事情,这(我相信)更容易理解,但生成的类型{f: X, g: Y} 比交集{f: X} &amp; {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&lt;&gt;需要能够区分功能与undefinedany,当你关闭--strictNullChecks时,这两者都会在不幸的地方弹出。这是因为undefined extends Function ? true : false 开始返回true,并且当您将手动undefined 值传递给函数时,any 开始被推断。区分undefined 相当简单,因为Function extends undefined ? true : false 仍然是false,但区分any 很烦人,并且涉及到一些funny business

再次祝你好运!

【讨论】:

  • 我总是可以依靠你来回答我令人费解的 Typescript 问题 :) 所以澄清一下,没有--strictNullChecks(除了重载)就没有办法做到这一点,对吧?
  • 并不是说“没有办法”完成它;它甚至更丑陋。更新了我的答案。
猜你喜欢
  • 2020-01-24
  • 1970-01-01
  • 2019-04-20
  • 2022-01-11
  • 2018-08-28
  • 1970-01-01
  • 1970-01-01
  • 2021-12-30
  • 2016-11-01
相关资源
最近更新 更多