I want wrap callback function and extend custom arguments, And i get error type alert
const func = (a: string) => {
console.log(a);
}
type Options = {
force?: boolean
};
const wrapFunc = <A extends any[], R>(fn: (...args: A) => R) => {
const wrappedFunc = (...args: A & [options?: Options]) => {
const options: Options = 'force' in args[args.length - 1] ? args.pop() : {};
fn(...(args as A))
}
return wrappedFunc;
}
const newFunc = wrapFunc(func);
newFunc('a', {force: true})
// Argument of type '["a", { force: true; }]' is not assignable to parameter of type '[a: string] & // [options?: Options | undefined]'.
// Type '["a", { force: true; }]' is not assignable to type '[a: string]'.
// Source has 2 element(s) but target allows only 1.(2345)
here is reproduced TypeScript playground
Could somebody help me make is right. Thanks!
The &
operator is not what you should use. number[] & {prop: string}
will expect an array with a prop
property typed as a string
, not another element. To push an element to a generic array parameter, you can do as follows:
<A extends unknown[]>(args: [...A, Options]
This one isn't suitable for us since we want to have optional options, and it can be achieved by telling the compiler that we expect A
or A
with options:
args: [...A, Options] | A
Implementation:
const wrapFunc = <A extends any[], R>(fn: (...args: A) => R) => {
const wrappedFunc = (...args: [...A, Options] | A) => {
const options: Options = 'force' in args[args.length - 1] ? args.pop() : {};
fn(...(args.slice(-1) as A));
};
return wrappedFunc;
};
// const newFunc: (...args: [a: string] | [string, Options]) => void
const newFunc = wrapFunc(func);
This works as expected; however, the label of a
is removed in the hinting when you also accept Options
. To fix it, we should use labeled tuples as follows:
const wrappedFunc = (...args: [...mainArgs: A, options: Options] | A) => {}
This way hinting will be better:
// const newFunc: (...args: [a: string] | [a: string, options: Options]) => void
const newFunc = wrapFunc(func);
Note: You can also consider using the following approach, however, it may throw some Typescript errors depending on your tsconfig:
const wrappedFunc = (...args: [...mainArgs: A, options?: Options]) => {}
// const newFunc: (a: string, options?: Options) => voi
const newFunc = wrapFunc(func);