我正在尝试创建一个通用的包装函数,它将包装传递给它的任何函数。
最基本的包装函数看起来像
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
模仿其包装函数的类型?
答案 0 :(得分:11)
可以通过进行2次更改来创建一个包装函数,该函数接受并返回与其包装函数相同的类型
T
泛型<any>
例如:
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>
获得任意返回类型包装后的函数现在将具有与内部函数相同的签名,几乎可以完成所有操作
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);
在我看来,这将更容易使用。