是否可以包装函数并保留其类型?

时间:2016-07-26 19:23:40

标签: typescript

我正在尝试创建一个通用的包装函数,它将包装传递给它的任何函数。

最基本的包装函数看起来像

function wrap<T extends Function>(fn: T) {
    return (...args) => {
        return fn(...args)
    };
}

我正在尝试使用它:

function foo(a: string, b: number): [string, number] {
    return [a, b];
}

const wrappedFoo = wrap(foo);

现在wrappedFoo正在获得(...args: any[]) => any

的类型

是否可以让wrappedFoo模仿其包装函数的类型?

5 个答案:

答案 0 :(得分:11)

可以通过进行2次更改来创建一个包装函数,该函数接受并返回与其包装函数相同的类型

  1. 将包装函数的返回值指定为要包装的T泛型
  2. 将您要返回的功能转换为<any>
  3. 例如:

    function wrap<T extends Function>(fn: T): T {
        return <any>function(...args) {
            return fn(...args)
        };
    }
    

    然后是const wrappedFoo = wrap(foo);

    的类型 然后

    将正确:

    (a: string, b: number) => [string, number].
    

答案 1 :(得分:6)

这适用于任意数量的参数,并保留所有参数和返回类型

const wrap = <T, U extends Array<T>, V>(fn: (...args: U) => V) => {
  return (...args: U): V => fn(...args)
}

答案 2 :(得分:6)

最近需要使用箭头功能执行此操作,提出了一种比其他一些答案更容易使用的方法,并解决了散布运算符和Symbol.iterator的问题(在ts 3.8中进行了测试)

type AnyFunction = (...args: any[]) => any;

const wrap = <Func extends AnyFunction>(
  fn: Func,
): ((...args: Parameters<Func>) => ReturnType<Func>) => {
  const wrappedFn = (...args: Parameters<Func>): ReturnType<Func> => {
      // your code here
      return fn(...args);
  };
  return wrappedFn;
};
  • AnyFunction是必需的,因此我们可以使用rest运算符并提取参数类型/返回类型
    • 然后使用Func extends AnyFunc来描述我们特别喜欢的任何函数的类型
  • 仅说结果为Func导致包装函数无法用其他子类型实例化,因此我们需要重新构造它
    • Parameters<Func>获取任意参数作为元组
      • 专门解决Symbol.iterator问题的方法是使用...Array.from(args)而不是...args
    • ReturnType<Func>获得任意返回类型
  • 您的IDE应该正确识别所有类型的包装函数

包装后的函数现在将具有与内部函数相同的签名,几乎可以完成所有操作

const foo = (a: string, b: number): number => a.length + b;
const wrappedFoo = wrap<typeof foo>(foo);

foo('hello', -5); // => 0
wrappedFoo('hello', -5); // => 0

答案 3 :(得分:2)

您可以使用overloads为包含0,1,2,3,4或更多参数的函数提供特定类型。如果你的一个函数需要更多的参数,添加一个额外的重载或者让它回退到其余参数的情况。

function wrap<TResult>(fn: () => TResult) : () => TResult;
function wrap<T1, TResult>(fn: (param1 : T1) => TResult) : (param1 : T1) => TResult;
function wrap<T1, T2, TResult>(fn: (param1 : T1, param2 : T2) => TResult) : (param1 : T1, param2 : T2) => TResult;
function wrap<T1, T2, T3, TResult>(fn: (param1 : T1, param2 : T2, param3 : T3) => TResult) : (param1 : T1, param2 : T2, param3 : T3) => TResult;
function wrap<T1, T2, T3, T4, TResult>(fn: (param1 : T1, param2 : T2, param3 : T3, param4 : T4) => TResult) : (param1 : T1, param2 : T2, param3 : T3, param4 : T4) => TResult;
function wrap<TParam, TResult>(fn: (...params : TParam[]) => TResult) : (...params : TParam[]) => TResult {
    return (...params) => {
        return fn(...params);
    };
}

它不是很漂亮,但确实提供了最准确的类型。

答案 4 :(得分:1)

这是可能的,但如果你想传递不同类型和数量的参数,可能会有点麻烦。

你的例子可以这样做:

function wrap<A, B, C>(fn: (a: A, b: B) => C) {
    return (a: A, b: B): C => {
        return fn(a, b);
    };
}

然后是:

的类型
const wrappedFoo = wrap(foo);

(a: string, b: number) => [string, number]code in playground

但正如您所看到的,如果您希望能够使用不同的签名(例如我的示例仅适用于两个参数),则使用起来并不是很舒服。

你能做的只是传递一个由界面支持的参数:

function wrap<In, Out>(fn: (params: In) => Out) {
    return (params: In): Out => {
        return fn(params);
    };
}

interface FooParams {
    a: string;
    b: number;
}

function foo(params: FooParams): [string, number] {
    return [params.a, params.b];
}

const wrappedFoo = wrap(foo);

code in playground

在我看来,这将更容易使用。