Is there a way to set up a function based on an initial interface? I have a bunch of listOfFunctions and a an equal amount of interfaces that contain types of the functions. I would like to create a factory that returns a custom hook. The idea is to save a ton of code and make editing and adding features to all the hooks a breeze.
FunctionInterface
Is all of the function names and their returns.
listOfFunctions
is a setup function (the real code has a config) that returns a object with a list of functions.
theChosenFunction
is a function that will be returned in the hook that allows me to interact with one of the listOfFunction functions. This means parameters (if needed). The functions in the real app are Promises that will set state of the hook. In this example, I just return a value and setState.
A) Is this possible to do with funcObject[funcName]()
and create a function to invoke with typescript? This would need that ability to add params or not.
B) Is there a way to get the return value of a function type? So in this example: type CanThisWork = () => string;
I want to extract string
; The idea is not to do a refactor on all of the listOfFunctions and FunctionInterfaces. There are a lot of them. If I have to create one complicated Factory, then I find this more economical.
interface FunctionInterface {
noParamsNoReturn: () => void;
noParamsNumberReturn: () => number;
propsAndNoReturn: (id: string) => void;
propsAndReturn: (id: string) => string;
}
const listOfFunctions = (): FunctionInterface => {
return {
noParamsNoReturn: () => void,
noParamsNumberReturn: () => 1,
propsAndNoReturn: (id: string) => console.log(id),
propsAndReturn: (id: string) => id,
}
}
function runThis<T>(funcName: keyof T, funcObject: T): void {
const [value, setValue] = React.useState();
// Can I have typescript set up the params here?
const theChosenFunction = ();
const theChosenFunction = (props) => {
// Can I have typescript invoke the function properly here?
const result = funcObject[funcName](); // funcObject[funcName](props)
if (result) {
setValue(result);
}
}
return {
theChosenFunction
}
}
const result = runThis<FunctionInterface>('noParamsNoReturn', listOfFunctions());
result.theChosenFunction()
// OR
result.theChosenFunction('someId');
There are built-in Parameters<T>
and ReturnType<T>
utility types that take a function type T
and use conditional type inference to extract the tuple of parameters and the return type, respectively:
type SomeFuncType = (x: string, y: number) => boolean;
type SomeFuncParams = Parameters<SomeFuncType>; // [x: string, y: number]
type SomeFuncRet = ReturnType<SomeFuncType>; // boolean
As for your runThis()
function, I'd be inclined to give it the following typings and implementation, assuming you want the minimal changes that compile and run:
function runThis<K extends PropertyKey, F extends Record<K, (...args: any[]) => any>>(
funcName: K, funcObject: F
) {
const [value, setValue] = React.useState();
const theChosenFunction = (...props: Parameters<F[K]>): void => {
const result = funcObject[funcName](...props);
if (result) {
setValue(result);
}
}
return {
theChosenFunction
}
}
I've given the function two generic type parameters: K
, corresponding to the type of funcName
, and F
, corresponding to the type of funcObject
. The constraints K extends PropertyKey
and F extends Record<K, (...args: any[])=>any>
guarantee that funcName
will be of a key-like type ( string
, number
, or symbol
), and that funcObject
will have a function-valued property at that key.
Then, we can make theChosenFunction
use spread and rest syntax to allow the function to be called with a variadic list of parameters. So (...props) => funcObject[funcName](...props)
will accept any number of parameters (including zero) and pass them to the called function. TypeScript represents such lists-of-parameters as a tuple type. Therefore, having theChosenFunction
's call signature look like (...props: Parameters<F[K]>) => void
means that it will accept the same parameters as the entry in funcObject
at the key funcName
, and that it will not output anything (because your implementation doesn't output anything).
Let's see if it works:
const result = runThis('noParamsNoReturn', listOfFunctions());
result.theChosenFunction(); // okay
result.theChosenFunction('someId'); // error! Expected 0 arguments, but got 1.
const anotherResult = runThis('propsAndReturn', listOfFunctions());
anotherResult.theChosenFunction(); // error! Expected 1 arguments, but got 0.
anotherResult.theChosenFunction("someId"); // okay
runThis(listOfFunctions(), 'someId'); // error! '
// FunctionInterface' is not assignable to 'string | number | symbol'.
runThis('foo', listOfFunctions()); // error!
// Property 'foo' is missing in type 'FunctionInterface'
runThis(
'bar',
{ bar: (x: string, y: number, z: boolean) => { } }
).theChosenFunction("hey", 123, true); // okay
Looks good to me.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.