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");
答案 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();
}
如果你采用这种方法,那么我建议将REGISTRY
作为一个包含集合的对象,这样你就可以只添加ctor并从中获取名称。
这有一个变体,那就是使用装饰器:
function register(constructor: typeof Component) {
REGISTRY[(constructor as any).name] = constructor;
}
@register
class MyComponent1 extends Component {}
@register
class MyComponent2 extends Component {}
(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();
}
如果您采用这种方法,那么您需要确保导出所有组件类。
(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();
}
这不是推荐的方法,您可以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