typescript get "typeof" Generic class inside type declaration

239 Views Asked by At

I'm trying to make a custom hook that gets 2 parameters:
1- a class that's actually is a mobx store
2- parameters of that class constructor
this hook is lookalike this in javascript:

/**
 * @template T
 * @param {new() => T} Store 
 * @param {any[]} initializers
 * @returns {T} an instance of the Mobx store/viewModel
 */
export const useMobx = (Store, initializers) => {
    const vm = useLazyRef(() => new Store(...initializers));
    return vm.current;
};

I just trying to rewrite it in typescript but it gets really complicated and after a lots of searches I can make it like this:

interface ClassWithConstructor<T>{
    new (name:string):T ;
}
type fnArgs<U,T extends ClassWithConstructor<U>> = ConstructorParameters<T> extends any[] ? ConstructorParameters<T> : never;
type UseMobxType = <U,T extends ClassWithConstructor<U>,>(Store: new(...args: fnArgs<U,T>) => U, initializers:fnArgs<U,T>) => U;
export const useMobx:UseMobxType = (Store, initializers) => {
    const vm = useLazyRef(() => new Store(...initializers));
    return vm.current;
};

it's works without any problem but usage was a little stupid:

useMobx<AppStatusListViewModel, typeof AppStatusListViewModel>(AppStatusListViewModel,["param1"]);

as you can see I have to pass 2 generic classes AppStatusListViewModel and typeof AppStatusListViewModel and I am wondering is there any better solution to make it easier to use for example like this:

useMobx<AppStatusListViewModel>(AppStatusListViewModel,["param1"]);

or even this:

useMobx(AppStatusListViewModel,["param1"]);

for more info useLazyRef source:

type initValFunctionType = ()=>any;
export const useLazyRef = (initValFunc:initValFunctionType) => {
    const ref:React.MutableRefObject<any> = useRef(null);
    if (ref.current === null) {
        ref.current = initValFunc();
    }
    return ref;
};

and consider AppStatusListViewModel is just a simple ES6 class:

class AppStatusListViewModel{
  constructor(name:string){
    console.log(name);
  }
}
1

There are 1 best solutions below

0
On

It seems you have over-complicated the type definitions for the custom hook.

The following type definitions should stand enough with proper type inferences & IDE intellisense.

import { useRef } from 'react'

/**
 * @param initValFunc class instantiator function
 */
export const useLazyRef = <T>(initValFunc: () => T) => {
    const ref = useRef<T | undefined>()
    if (ref.current === undefined) {
        ref.current = initValFunc()
    }
    return ref
}

type UseMobxType = <U, T>(
    Store: new (...args: T[]) => U,
    initializers?: T[]
) => U

export const useMobx: UseMobxType = (Store, initializers = []) => {
    const vm = useLazyRef(() => new Store(...initializers))
    return vm.current!
}

you can check for the result & do custom manipulations using the following sandbox:

https://codesandbox.io/s/usemobx-ww79oe