4

如何在不更改 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'
4

4 回答 4

7

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

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

目前没有一种很好的方法可以在打字稿中保存函数签名。但是,对于以后的版本有一个新的提议可以解决这个问题: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/ 。

于 2017-07-10T20:32:27.307 回答
7

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

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

wrapper用 调用时x,TS 编译器将其类型推断为function wrapper<[string], void>(operation: (operationParameters_0: string) => void, parameters_0: string): void.

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

于 2020-07-29T01:01:44.247 回答
0

这是我找到的另一种方式。它甚至保留了包装函数的泛型:

const newFunction = ((one, two) => {
    // something before
    const result = oldFunction(one, two)
    // something after
    return result
}) as typeof oldFunction
于 2022-03-04T02:40:00.743 回答
0

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


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."
于 2022-01-17T01:32:39.650 回答