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

时间:2016-08-23 15:36:42

标签: javascript typescript

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 type parameter by adding | string to it, but it now gives me errors saying:

Cannot use 'new' with an expression whose type lacks a call or construct signature.

Is there anyway for me to modify this so that I can pass either a Class name in or a string version of the class?

Here is what I have that doesn't work:

public addComponent<T extends Component>(type: ComponentType<T> | string): T {
    let comp;
    comp = new type() as T;
    comp.name = comp.constructor.name;
}

Here are its dependencies:

class Component extends Obj {

}

interface ComponentType<T extends Component> {
    new(): T;
}

I have tried using Object.create(), which works fine, but then I get a new error:

Uncaught TypeError: Cannot assign to read only property 'name' of object '[object Object]'

Edit:

In the end I would like to be able to pass the following to addComponent:

obj.addComponent(MyClass);

Or

obj.addComponent("MyClass");

2 个答案:

答案 0 :(得分:3)

没有办法在javascript中使用名称来获取类,它没有类似于java ClassLoader的内容。
您可以通过创建自己的机制来解决这个问题,并且可能有很多方法可以实现,但这里有3个选项。

(1)维护组件类的注册表:

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

如果你采用这种方法,那么我建议将REGISTRY作为一个包含集合的对象,这样你就可以只添加ctor并从中获取名称。

这有一个变体,那就是使用装饰器:

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)将组件包装在命名空间中(正如@Shilly在评论中建议的那样):

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

如果您采用这种方法,那么您需要确保导出所有组件类。

(3)使用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

这不是推荐的方法,您可以read all about the cons在很多地方使用eval
但它仍然是一个选项,所以我列出它。

答案 1 :(得分:1)

如果类在名称空间中,则可以通过名称将类实例化为String:

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

例如,这应该有效:

namespace myNamespace {
    export class myClass  {
        example() {
            return true;
        }
    }
}

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

这将使用字符串myClass实例化类"myClass"

因此,回到你的情况,我认为这将有效:

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

然后,你将能够做到这一点:

let stringToLoad = "Component";
let classToLoad = Component;

let example = new Example();

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

带代码+测试的游乐场版本:here