代码之家  ›  专栏  ›  技术社区  ›  ethan.roday

Typescript推断执行具有相同参数签名的可选函数参数的高阶函数的类型

  •  0
  • ethan.roday  · 技术社区  · 7 年前

    假设我有一个函数,它包含两个函数 f g 作为参数,并返回执行的函数 F 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 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... */ }
    
    1 回复  |  直到 7 年前
        1
  •  2
  •   jcalz    7 年前

    假设你有 --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 .

    另外,对于返回函数的返回类型,我做了一个有点难看的映射类型。我首先计划做一些类似的事情 IfFun<F, {f: Ret<F>}> & IfFun<G, {g: Ret<G>}> ,这(我相信)更容易理解,但结果是 {f: X, g: Y} 比十字路口好 {f: X} & {g: Y} .

    不管怎样,希望这有帮助。祝你好运


    --严格的零支票 关闭后,定义变得更加复杂:

    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 ,当您关机时,这两个选项都会在不幸的地方弹出 --严格的零支票 undefined extends Function ? true : false 开始返回 true ,及 当您通过手册时开始推断 将值转换为函数。辨别 未定义 Function extends undefined ? true : false false ,但有区别 任何 funny business .

    祝你好运!

    推荐文章