I have a typescript file containing a list of typed exported functions that looks like that:
// File hooks.tsx
export function useBookQuery(...) {
...
}
export function useAuthorQuery(...) {
...
}
I also have a class defined as follow (it stores the various exported function from the previous file and resolve function from a type).
// File TypeRegistry.tsx
class TypeRegistry {
constructor(hooks) {
this.hooks = hooks
}
getHook(type: string) {
return this.hooks[`use${type}Query`];
}
}
The class is instanced as follow:
import * as hooks from './hooks'
const typeRegistry = new TypeRegistry(hooks);
const queryBook = typeRegistry.getHook('Book');
const queryAuthor = typeRegistry.getHook('Author');
Here, I want the queryBook
variable type to be the one of the useBookQuery
from the hooks.tsx
file and the queryAuthor
to be the type of useAuthorQuery
.
I don't know how to type the getHook
method to achieve the desired behavior and I don't know if it's even possible.
CodePudding user response:
You can type this using typeof import
to get the type of the import. You can then manipulate this type as you would any other. In this case getHook
will need to be generic, and we will need to use template literal types and conditional types to extract the union of possible values for type
:
// @filename: TypeRegistry.tsx
type AllHooks = typeof import('./hooks');
type ExtractName<T extends `use${string}Query`> = T extends `use${infer Type}Query` ? Type: never
class TypeRegistry {
hooks: AllHooks;
constructor(hooks: AllHooks) {
this.hooks = hooks
}
getHook<K extends ExtractName<keyof AllHooks>>(type: K): AllHooks[`use${K}Query`] {
return this.hooks[`use${type}Query`];
}
}
// @filename: usage.tsx
import * as hooks from './hooks'
const typeRegistry = new TypeRegistry(hooks);
const queryBook = typeRegistry.getHook('Book'); // correct type for book
const queryAuthor = typeRegistry.getHook('Author'); // correct type for author
You can also make TypeRegistry
generic, so the type of hooks
can be passed in when you build the registry:
type ExtractName<T extends string> = T extends `use${infer Type}Query` ? Type: never
class TypeRegistry<T extends Record<`use${string}Query`, () => any>> {
hooks: T;
constructor(hooks: T) {
this.hooks = hooks
}
getHook<K extends ExtractName<keyof T & string>>(type: K): T[`use${K}Query`] {
return this.hooks[`use${type}Query`];
}
}