I am trying to create a function that receives two arguments: a callback function, and the parameters (props) for that function. For example:
const callbackFunction = (a: number) => a + 2;
const callbackFunctionProps = 5;
callbackWrapper(callbackFunction, callbackFunctionProps);
I want TypeScript to check that the props given as 'callbackFunctionProps' are valid props for 'callbackFunction'. Is there a way to do this without using generics? it does not matter what are the types of the function and props as long as they fit each other.
Generics are exactly the remedy for "it does not matter what are the types" cases. Here's one possible way to do that with generics:
const callbackWrapper = <I extends Array<unknown>, O>(fn: (...fnArgs: I) => O, ...args: I): O =>
{
const result = fn(...args);
console.log(`CALLED WITH ${args.join(', ')}, RESULT IS ${result}`);
return result;
}
const callbackFunctionA = (a: number) => a + 2;
const callbackFunctionPropsA = 5;
const callbackFunctionB = (a: number, b: number) => a + b + 2;
const callbackFunctionPropsB1 = 10;
const callbackFunctionPropsB2 = 30;
callbackWrapper(callbackFunctionA, callbackFunctionPropsA);
// "CALLED WITH 5, RESULT IS 7"
callbackWrapper(callbackFunctionB, callbackFunctionPropsB1, callbackFunctionPropsB2);
// "CALLED WITH 10, 30, RESULT IS 42"
The trick is using I (type-param of created generic) in two places: as a type of passed function's param (...fnArgs: I part) and as a type of params passed into your callback (...args: I part). If they do not match, the compiler will yell at you:
callbackWrapper(callbackFunctionA, '2'); // Argument of type 'string' is not assignable to parameter of type 'number'
callbackWrapper(callbackFunctionA, 2, 3); // Expected 2 arguments, but got 3.
callbackWrapper(callbackFunctionB, 2); // Expected 3 arguments, but got 2.
function callbackWrapper<T extends any[]>(
callbackFunction: (...arg: T) => any,
...callbackFunctionProps: T
) {
console.log(callbackFunction(...callbackFunctionProps));
}
function callbackFunction1(a: number) {
return a + 2;
}
const callbackFunctionProps1 = 5;
callbackWrapper(callbackFunction1, callbackFunctionProps1);
function callbackFunction2(a: string) {
return a + ' world';
}
const callbackFunctionProps2 = 'hello';
callbackWrapper(callbackFunction2, callbackFunctionProps2);