Можно ли обернуть функцию и сохранить ее типы?
Я пытаюсь создать общую функцию обертки, которая будет обертывать любую переданную ему функцию.
В самом начале функция обертки будет выглядеть примерно так:
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
чтобы имитировать типы этой функции?
Ответы
Ответ 1
Можно создать функцию-оболочку, которая принимает и возвращает те же типы, что и функция, ее обертывание, делая 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].
Ответ 2
Это работает с любым количеством аргументов и сохраняет все аргументы & типы возврата
const wrap = <T extends Array<any>, U>(fn: (...args: T) => U) => {
return (...args: T): U => fn(...args)
}
Ответ 3
Вы можете использовать перегрузки для предоставления определенных типов для обертывания функций с параметрами 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
Это возможно, но может стать немного беспорядочным, если вы хотите иметь возможность передавать разные типы и количество аргументов.
Ваш пример можно сделать следующим образом:
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);
Is (a: string, b: number) => [string, number]
.
((fn: (a: A, b: B) => C) {
return (a: A, b: B): C => {
return fn(a, b);
};
}
function foo(a: string, b: number): [string, number] {
return [a, b];
}
const wrappedFoo = wrap(foo); rel=nofollow>код на детской площадке)
Но, как вы можете видеть, работать с ними не очень удобно, если у вас есть разные подписи, которые вы хотите использовать (например, мой пример работает только для двух параметров).
Что вы можете сделать, это передать только один параметр, который поддерживается интерфейсом:
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);
((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); rel=nofollow>код на детской площадке)
По моему мнению, с этим будет гораздо легче работать.