I'm trying to figure out the correct type for a utility function getProp
that will index an object by key and return the primitive value at that index or the result of the function at that index.
declare interface App {
name: () => string
version: string
id: number
}
const getProp = function<C extends App, S extends keyof App>(context: C, prop: S) : C[S] extends (...args: any[]) => any ? ReturnType<C[S]> : C[S] {
const method = context[prop];
if (typeof method === 'function') {
return method(); // Error: Type 'string' is not assignable to type 'C[S] extends (...args: any[]) => any ? ReturnType<C[S]> : C[S]'.
} else {
return method; // Error: Type '() => string' is not assignable to type 'C[S] extends (...args: any[]) => any ? ReturnType<C[S]> : C[S]'.
}
};
declare var app : App;
// These are all correct despite the errors
getProp(app, "id") // => number
getProp(app, "name") // => string
getProp(app, "version") // => string
The type presented here "works" but TypeScript is giving me errors on the lines indicated. Is there a better way to type this properly without errors or should I just drop in my old friend //@ts-ignore
?
When we use
Generic Functions
the actual type instantiation happens when we invoke it,getProp<App, 'name'>
Hence the Typescript compiler needs to be sure that the types we are using must be compatible and follow the constraints.
Since the Return Type of
getProp
is conditional we can make use of assertion/aliasing to inform the typescript compiler of the state in which this branch will be reachedIn your code, the compiler was complaining that it is possible that you are returning some code (which might not match the return value) at the time of declaration!
Hence by aliasing it, we make sure that the types will be of this ReturnType only!
To avoid Repetition I created a new type alias
PropReturnType<T>
CODE PLAYGROUND