How to pass a string or class to a method to create instance

前端 未结 2 1569
心在旅途
心在旅途 2021-01-21 12:37

I am to use the following method, it works by passing a type to it such as obj.addComponent(MyClass). This works just fine.

I tried to modify the typ

相关标签:
2条回答
  • 2021-01-21 13:15

    There's no way to get the class using a name in javascript, it doesn't have something similar to the java ClassLoader.
    You can get around that by creating your own mechanism, and there are probably many ways to do so, but here are 3 options.

    (1) Maintain a registry for your component classes:

    const REGISTRY: { [name: string]: ComponentType<Component> } = {};
    
    class Component {}
    
    class MyComponent1 extends Component {}
    REGISTRY["MyComponent1"] = MyComponent1;
    
    class MyComponent2 extends Component {}
    REGISTRY["MyComponent2"] = MyComponent2;
    
    type ComponentType<T extends Component> = {
        new(): T;
    }
    
    function factory<T extends Component>(type: ComponentType<T> | string): T {
        return typeof type === "string" ?
            new REGISTRY[type]() as T:
            new type();
    }
    

    (code in playground)

    If you go with this approach then I suggest to make the REGISTRY an object that holds the collection, that way you can add the ctor only and get the name from that.

    There's a variant for this and that's to use a decorator:

    function register(constructor: typeof Component) {
        REGISTRY[(constructor as any).name] = constructor;
    }
    
    @register
    class MyComponent1 extends Component {}
    
    @register
    class MyComponent2 extends Component {}
    

    (code in playground)

    (2) Wrap the components in a namespace (As @Shilly suggested in a comment):

    namespace components {
        export class Component {}
        export class MyComponent1 extends Component {}
        export class MyComponent2 extends Component {}
    
        export type ComponentType<T extends Component> = {
            new(): T;
        }
    
        export function forName(name: string): ComponentType<Component> {
            if (this[name] && this[name].prototype instanceof Component) {
                return this[name];
            }
        }
    }
    
    function factory<T extends components.Component>(type: components.ComponentType<T> | string): T {
        return typeof type === "string" ?
            new (components.forName(type))() as T:
            new type();
    }
    

    (code in playground)

    If you're going with this approach then you need to make sure that all the component classes are exported.

    (3) Use eval

    class Component {}
    class MyComponent1 extends Component {}
    class MyComponent2 extends Component {}
    
    type ComponentType<T extends Component> = {
        new(): T;
    }
    
    function factory<T extends Component>(type: ComponentType<T> | string): T {
        return typeof type === "string" ?
            new (eval(type))() as T:
            new type();
    }
    

    (code in playground)

    This isn't a recommended approach, and you can read all about the cons in using eval in a lot of places.
    But it's still an option so I'm listing it.

    0 讨论(0)
  • 2021-01-21 13:24

    There is a way to instantiate classes by their name as String if they are in a namespace :

    var variableName: any = new YourNamespace[YourClassNameString](ClassParameters);
    

    For exmaple, this should work :

    namespace myNamespace {
        export class myClass  {
            example() {
                return true;
            }
        }
    }
    
    var newClass: any = new myNamespace["myClass"](); // <- This loads the class A.
    newClass.example();
    

    This will instantiate the class myClass using the string "myClass".

    Thus, to come back to your situation, I think this will work :

    namespace myNamespace {
    
        // The dependencies you defined
        export class Component {
    
        }
        export interface ComponentType<T extends Component> {
            new(): T;
        }
    
        // Just a class to contain the method's code
        export class Example {
            public addComponent<T extends Component>(type: ComponentType<T> | string): T {
                let result: T;
                if (typeof type === "string") {
                    result = new myNamespace[type]();
                } else {
                    result = new type();
                }
                return result;
            }
        }
    }
    

    Then, you'll be able to do this :

    let stringToLoad = "Component";
    let classToLoad = Component;
    
    let example = new Example();
    
    let result1: Component = example.addComponent(stringToLoad);
    let result2: Component = example.addComponent(classToLoad);
    

    Playground version with code + test : here

    0 讨论(0)
提交回复
热议问题