Home > database >  Typescript dynamic runtime type
Typescript dynamic runtime type

Time:02-09

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

Workbench link

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`];
    }
}

Workbench link

  •  Tags:  
  • Related