typescript 动态加载打字稿类(打字稿的反射)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15338610/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-11 01:01:31  来源:igfitidea点击:

Dynamically loading a typescript class (reflection for typescript)

javascripttypescript

提问by Greg van Berkel

I would like to be able to instantiate a typescript class where I get the class and constructor details at runtime. The function I would like to write will take in the class name and constructor parameters.

我希望能够实例化一个打字稿类,在那里我可以在运行时获取类和构造函数的详细信息。我想编写的函数将接受类名和构造函数参数。

export function createInstance(moduleName : string, className : string, instanceParameters : string[]) {
    //return new [moduleName].[className]([instancePameters]); (THIS IS THE BIT I DON'T KNOW HOW TO DO)
}

采纳答案by Colin Dumitru

You could try:

你可以试试:

var newInstance = Object.create(window[className].prototype);
newInstance.constructor.apply(newInstance, instanceparameters);
return newInstance;

EditThis version is working using the TypeScript playground, with the example:

编辑此版本正在使用 TypeScript playground,示例如下:

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

//instance creation here
var greeter = Object.create(window["Greeter"].prototype);
greeter.constructor.apply(greeter, new Array("World"));

var button = document.createElement('button');
button.innerText = "Say Hello";
button.onclick = function() {
    alert(greeter.greet());
}

document.body.appendChild(button);

回答by Fenton

As you are using TypeScript I'm assuming you want the loaded object to be typed. So here is the example class (and an interface because you are choosing to load one of many implementations, for example).

当您使用 TypeScript 时,我假设您希望输入加载的对象。所以这里是示例类(和一个接口,因为您选择加载许多实现之一,例如)。

interface IExample {
    test() : string;
}

class Example {
    constructor (private a: string, private b: string) {

    }

    test() {
        return this.a + ' ' + this.b;
    }
}

So you would use some kind of loader to give you back an implementation:

所以你会使用某种加载器来返回一个实现:

class InstanceLoader {
    constructor(private context: Object) {

    }

    getInstance(name: string, ...args: any[]) {
        var instance = Object.create(this.context[name].prototype);
        instance.constructor.apply(instance, args);
        return instance;
    }
}

And then load it like this:

然后像这样加载它:

var loader = new InstanceLoader(window);

var example = <IExample> loader.getInstance('Example', 'A', 'B');
alert(example.test());

At the moment, we have a cast: <IExample>- but when generics are added, we could do away with this and use generics instead. It will look like this (bearing in mind it isn't part of the language yet!)

目前,我们有一个演员表:<IExample>- 但是当添加泛型时,我们可以取消这个并使用泛型代替。它看起来像这样(记住它不是语言的一部分!)

class InstanceLoader<T> {
    constructor(private context: Object) {

    }

    getInstance(name: string, ...args: any[]) : T {
        var instance = Object.create(this.context[name].prototype);
        instance.constructor.apply(instance, args);
        return <T> instance;
    }
}

var loader = new InstanceLoader<IExample>(window);

var example = loader.getInstance('Example', 'A', 'B');

回答by Gone Coding

Update

更新

To get this to work in latest TypeScript you now need to cast the namespace to any. Otherwise you get an Error TS7017 Build:Element implicitly has an 'any' type because type '{}' has no index signature.

要使其在最新的 TypeScript 中工作,您现在需要将命名空间强制转换为any. 否则你会得到一个Error TS7017 Build:Element implicitly has an 'any' type because type '{}' has no index signature.

Ifyou have a specific namespace/module, for all the classes you want to create, you can simply do this:

如果你有一个特定的命名空间/模块,对于你想要创建的所有类,你可以简单地这样做:

var newClass: any = new (<any>MyNamespace)[classNameString](parametersIfAny);

Update: Without a namespace use new (<any>window)[classname]()

更新:没有命名空间使用 new (<any>window)[classname]()

In TypeScript, if you declare a class outside of a namespace, it generates a var for the "class function". That means it is stored against the current scope (most likely windowunless you are running it under another scope e.g. like nodejs). That means that you can just do new (<any>window)[classNameString]:

在 TypeScript 中,如果你在命名空间之外声明一个类,它会为“类函数”生成一个 var。这意味着它是针对当前范围存储的(很可能,window除非您在另一个范围下运行它,例如 nodejs)。这意味着你可以这样做new (<any>window)[classNameString]

This is a working example (all code, no namespace):

这是一个工作示例(所有代码,没有命名空间):

class TestClass
{
    public DoIt()
    {
        alert("Hello");
    }
}

var test = new (<any>window)["TestClass"]();
test.DoIt();

To see why it works, the generated JS code looks like this:

要了解它的工作原理,生成的 JS 代码如下所示:

var TestClass = (function () {
    function TestClass() {
    }
    TestClass.prototype.DoIt = function () {
        alert("Hello");
    };
    return TestClass;
}());
var test = new window["TestClass"]();
test.DoIt();

回答by aleung

This works in TypeScript 1.8 with ES6 module:

这适用于带有 ES6 模块的 TypeScript 1.8:

import * as handlers from './handler';

function createInstance(className: string, ...args: any[]) {
  return new (<any>handlers)[className](...args);
}

Classes are exported in handlermodule. They can be re-exported from other modules.

类在handler模块中导出。它们可以从其他模块重新导出。

export myClass {};
export classA from './a';
export classB from './b';

As for passing module name in arugments, I can't make it work because ES6 module is unable to be dynamic loaded.

至于在参数中传递模块名称,我无法使其工作,因为 ES6 模块无法动态加载。

回答by Joe

As of typescript 0.9.1, you can do something like this playground:

作为打字稿0.9.1的,你可以做这样的事情的游乐场

class Handler {
    msgs:string[];  
    constructor(msgs:string[]) {
        this.msgs = msgs;
    }
    greet() {
        this.msgs.forEach(x=>alert(x));
    }
}

function createHandler(handler: typeof Handler, params: string[]) {
    var obj = new handler(params);
    return obj;
}

var h = createHandler(Handler, ['hi', 'bye']);
h.greet();

回答by Pian0_M4n

One other way would be calling the file dynamically and new

另一种方法是动态调用文件 new

// -->Import: it dynamically
const plug = await import(absPath);
const constructorName = Object.keys(plug)[0];

// -->Set: it
const plugin = new plug[constructorName]('new', 'data', 'to', 'pass');

回答by Maximiliano De Lorenzo

I've found another way as in my case I don't have access to window.

我找到了另一种方法,因为在我的情况下我无法访问 window.open 。

Example class that want to be created:

要创建的示例类:

class MyService {

  private someText: string;

  constructor(someText: string) {
    this.someText = someText;
  }

  public writeSomeText() {
    console.log(this.someText);
  }
}

Factory class:

工厂类:

interface Service<T> {
  new (param: string): T;
}

export class ServiceFactory<T> {

  public createService(ctor: Service<T>, param: string) {
    return new ctor(param);
  }

}

And then to create the instance using the Factory:

然后使用工厂创建实例:

const factory: ServiceFactory<MyService> = new ServiceFactory<MyService>();
const service: MyService = factory.createService(MyService, 'Hello World');
service.writeSomeText();

回答by PAX

If nothing helps you might abuse eval:

如果没有任何帮助,您可能会滥用eval

namespace org {
  export namespace peval {
    export class MyClass {
      constructor() {

      }

      getText(): string {
        return 'any text';
      }
    }
  }
}

const instance = eval('new org.peval.MyClass();');

console.log(instance.getText());

回答by Ivan Carmenates García

function fromCamelCase(str: string) {
  return str
    // insert a '-' between lower & upper
    .replace(/([a-z])([A-Z])/g, '-').toLowerCase();
}

async getViewModelFromName(name: string) {
    //
    // removes the 'ViewModel' part ('MyModelNameViewModel' = 'MyModelName').
    let index = name.indexOf('ViewModel');
    let shortName = index > 0 ? name.substring(0, index) : name;

    // gets the '-' separator representation of the camel cased model name ('MyModelName' = 'my-model-name').
    let modelFilename = fromCamelCase(shortName) + '.view-model';

    var ns = await import('./../view-models/' + modelFilename);

    return new ns[name]();
  }

or

或者

declare var require: any; // if using typescript.

getInstanceByName(name: string) {
    let instance;

    var f = function (r) {
      r.keys().some(key => {
        let o = r(key);
        return Object.keys(o).some(prop => {
          if (prop === name) {
            instance = new o[prop];
            return true;
          }
        })
      });
    }
    f(require.context('./../view-models/', false, /\.view-model.ts$/));

    return instance;
}