我要实现的目标是:给定随机签名的函数f
,假设f: (x: string, y: number): boolean
,我想创建一个满足以下条件的函数g
:
g: (x: string, y: number, callback?: (b: boolean) => void): void
f(x,y)
基本上,我想在不丢失类型信息的情况下回调任何函数。
这是我的看法:
type Callback<T> = (t: T) => void;
function withCallback<F extends(...args: any) => any>(
f: F,
) {
return function (...args: any) {
let callback: Callback<ReturnType<F>> | undefined;
let params: Array<any> = args;
if (args.length && typeof (args[args.length - 1]) === 'function') {
callback = args[args.length - 1];
params = args.slice(0, args.length - 1);
}
const result = f(...params);
callback(result);
} as (...args: Parameters<F>) => void; // ???
}
function f(a: string) { return 2 };
const g = withCallback(f);
代码有效:例如
console.log(f('a'));
g('a', console.log);
都将输出相同的内容。但是我不能正确输入。 g
具有正确的参数,但可选的回调参数除外,我似乎无法使其合适。更具体地说,我不知道要在标有???
的行中放入哪种类型。
答案 0 :(得分:2)
我也可以给出解决方案,其中没有什么是可选的(因此f
中的函数withCallback(f)
没有可选参数,并且调用withCallback(f)(...args, cb)
需要cb
)
问题是,您希望表示将元素V
附加到元组类型T
的 end 上的效果。我将其称为Push<T, V>
。 TypeScript不支持开箱即用。自rest tuples推出以来,TypeScript 确实支持在元组类型开始上的元素V
之前 {1}},但是;我将其称为Cons<V, T>
:
T
只要元组的元素不是optional,就可以主要根据// Prepend an element V onto the beginning of a tuple T.
// Cons<1, [2,3,4]> is [1,2,3,4]
type Cons<V, T extends any[]> = ((v: V, ...t: T) => void) extends ((
...r: infer R
) => void)
? R
: never;
和映射/条件类型来实现Push<T, V>
:< / p>
Cons<V, T>
(问题是// Append an element V onto the end of a tuple T
// Push<[1,2,3],4> is [1,2,3,4]
// note that this DOES NOT PRESERVE optionality/readonly in tuples.
// So unfortunately Push<[1, 2?, 3?], 4> is [1,2|undefined,3|undefined,4]
type Push<T extends any[], V> = (Cons<any, Required<T>> extends infer R
? { [K in keyof R]: K extends keyof T ? T[K] : V }
: never) extends infer P
? P extends any[] ? P : never
: never;
的工作原理是将元素(包括可选元素)向右移动,而Cons
最终将它们留在了已移动的位置,而这不是您所需要的也许有一天。将有一种受支持的方法来使Push
的行为完全符合期望,但就目前而言,这是我可以合理做到的最好的方法。
因此,带着Push
武装起来,这就是我们的前进方式:
Push
让我们看看它是否有效:
type Callback<T> = (t: T) => void;
function withCallback<F extends (...args: any) => any>(f: F) {
return function(...args: any[]) {
const params = args.slice(); // as Parameters<F>; <-- this doesn't help, unfortunately
const callback = params.pop() as Callback<ReturnType<F>>;
callback(f(...params));
} as (...args: Push<Parameters<F>, Callback<ReturnType<F>>>) => void;
}
对我很好。好的,希望对您有所帮助。祝你好运!
更新:这是通过可选回调前进的一种可能方法,该回调禁止最后一个参数为函数的函数。但是,该函数不能具有可选参数,至少不容易。
由于时间不够,我会让代码说明一切。祝你好运!
function f(a: string) {
return a.length;
}
const g = withCallback(f);
g("Hello", n => console.log(n - 2)); // okay, console logs 3
g("Goodbye", "oops"); // error!
// ~~~~~~ <-- "oops" is not assignable to Callback<number>