【问题标题】:Typescript wrapping function with generic type具有泛型类型的打字稿包装函数
【发布时间】:2017-12-14 17:01:05
【问题描述】:

如何在 Typescript 中包装函数而不更改其泛型类型?

function x() {
  console.log('Original Function');
}

function wrapper<T extends Function>(func: T): T {
  // Typescript compiler error:
  // Type '() => void' is not assignable to type 'T'.
  return () => {
    console.log('Wrapped Function');
    func.call(null);
  }
}

const xWrapped = wrapper(x);
xWrapped(); // logged 'Wrapped Function' & 'Original Function'

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案1】:

    包装函数应该接受一个函数并返回一个完全相同类型签名的函数。

    Typescript 无法知道传递的函数接受多少个参数或返回什么,并且您的函数隐式假定它接受 0 个参数(除了 this)并返回 void。

    目前没有一种很好的方法可以在 typescript 中保存函数签名。然而,有一个新的提议可以解决这个问题:https://github.com/Microsoft/TypeScript/issues/5453

    现在您可以制作一个看起来像这样的通用包装器。

    function wrapper<T extends (...args:any[])=>any>(func: T): T {
      return <T>((...args:any[]) => {
            console.log('Wrapped Function');
            return func(...args);
        });
    }
    

    有了可变参数类型的提议,这个函数可以写成这样

    function wrapper<...TArgs,TRet>(func:(...args:...TARGS)=>TRet) {
        return (...args:...TARGS) => {
            console.log("Wrapped function");
            return func(...args);
        }
    }
    

    注意这里的主要区别是上面的解决方案没有强制转换必须告诉编译器返回变量与输入变量的类型相同。然而,对于可变参数类型,参数本身可以被一般地输入。 (请注意,可变参数类型目前不在打字稿中,当确实包含在内时,上述代码完全有可能存在语法错误)

    TypeScript 4.3 版本更新: 包装器的已实施提案工作代码将如下所示

    type InferArgs<T> = T extends (...t: [...infer Arg]) => any ? Arg : never;
    type InferReturn<T> = T extends (...t: [...infer Arg]) => infer Res ? Res : never;
    
    function getWrapper<TFunc extends (...args: any[]) => any>(func: TFunc)
        : (...args: InferArguments<TFunc>) => InferReturn<TFunc> {
    
        return (...args: InferArguments<TFunc>) => {
            // something before
    
            try {
                return func(...args);
            } finally {
    
                // something after;
            }
        };
    }
    

    可以在此处找到有关使用可变参数元组的一些简短但有用的信息和示例:https://fettblog.eu/variadic-tuple-types-preview/

    【讨论】:

    • 此提案将如何解决此问题?
    • @PRAISER 您可以为函数参数指定泛型类型列表。该示例显示了完全通用的咖喱/部分应用程序功能的使用。这意味着 fn.bind 和 fn.apply 之类的东西在未来有望成为类型安全的。
    【解决方案2】:

    这是一种替代方法,它保留参数并返回内部函数的类型,而不依赖于可变参数类型。

    function x(message: string): void {
        console.log(`inner ${message}`);
    }
    
    export function wrapper<Args extends any[], Return>(
        operation: (...operationParameters: Args) => Return, 
        ...parameters: Args
    ): Return {
        console.log(`outer `);
    
        return operation(...parameters);
    }
    
    x("abc");
    wrapper(x, "xyz");
    
    // output:
    //
    // inner abc
    // outer
    // inner xyz
    

    当使用x 调用wrapper 时,TS 编译器将其类型推断为function wrapper&lt;[string], void&gt;(operation: (operationParameters_0: string) =&gt; void, parameters_0: string): void

    如果您尝试调用 wrapper(x, 123),它会因漂亮的类型安全而失败:Argument of type '123' is not assignable to parameter of type 'string'.

    【讨论】:

      【解决方案3】:

      我更喜欢更简单的解决方案,如下所示:

      
      function wrapFunction<TArgs extends any[], TReturn>(
        targetFunction: (...parameters: TArgs) => TReturn,
      ): (...parameters: TArgs) => TReturn {
        return (...parameters: TArgs) => {
          console.log(`Hello, what is your name?`);
          return targetFunction(...parameters);
        };
      }
      
      
      // --------------- Example
      const someFunction = (name: string) => {
          console.log(`Hey! My name is ${name}.`);
      }
      
      const wrappedFunction = wrapFunction(someFunction);
      wrappedFunction("Fábio");
      

      输出

      [LOG]: "Hello, what is your name?" 
      [LOG]: "Hey! My name is Fábio." 
      

      或者,如果你想要更通用的东西:

      export function wrapFunction<TArgs extends any[], TReturn>(
        targetFunction: (...parameters: TArgs) => TReturn,
        wrapperFunction: (...parameters: TArgs) => void,
      ): (...parameters: TArgs) => TReturn {
        return (...parameters: TArgs) => {
          wrapperFunction(...parameters);
          return targetFunction(...parameters);
        };
      }
      
      
      
      // --------------- Example
      const someFunction = (name: string) => {
          console.log(`Hey! My name is ${name}.`);
      }
      
      const wrapperFunction = (name: string) => {
          console.log(`The wrapper - Hey! My name is ${name}.`);
      }
      
      const wrappedFunction = wrapFunction(someFunction, wrapperFunction);
      wrappedFunction("Fábio");
      
      

      输出

      [LOG]: "The wrapper - Hey! My name is Fábio." 
      [LOG]: "Hey! My name is Fábio."
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-07-08
        • 1970-01-01
        • 2023-02-22
        • 2021-11-26
        • 2020-08-03
        • 1970-01-01
        • 2020-03-22
        相关资源
        最近更新 更多