【问题标题】:Dynamically loading a typescript class (reflection for typescript)动态加载一个打字稿类(打字稿的反射)
【发布时间】:2013-02-26 15:08:39
【问题描述】:

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

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

【问题讨论】:

标签: javascript typescript


【解决方案1】:

你可以试试:

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

编辑此版本正在使用 TypeScript 操场,例如:

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

【讨论】:

  • 这很好;虽然我建议不要明确调用window。即,如果他使用 NodeJs 怎么办?而且window对象不存在?
  • 他不能用全局替换 NodeJs 的 window 吗?
  • 需要一些额外的东西才能使模块部分正常工作(原始问题中没有指定)。 var newInstance = Object.create(window["moduleName1"]["moduleName"]["ClassName"].prototype);替换 var person = new entities.Mammal.Person(); 之类的东西
  • 在 TypeScript 1.8.9 和 SystemJS 中,这不起作用,因为 window 不包含导出的类。
  • 您可以使用命名空间,而不是使用“窗口”。例如导出命名空间模型 { 导出类 Greeter{...} } 。然后你可以使用 Object.create(Model['Greeter'].prototype);
【解决方案2】:

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

interface IExample {
    test() : string;
}

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

    }

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

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

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

然后像这样加载它:

var loader = new InstanceLoader(window);

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

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

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');

【讨论】:

  • +1 的解决方案也适用于最新的 TypeScript。我必须添加演员才能让我的版本现在可以工作:)。
  • 这对我有用。我唯一要补充的是关于命名空间的一些说明(如上图所示)。在此示例中: const il = new InstanceLoader(Model); const 员工 = il.getInstance('Employee');模型是一个导出的命名空间。例如export namespace Model { export class Employee{...} } 然后当你想从命名空间模型中加载 Employee 时, import { Model } from '.your_model_path' 所以你可以将你的类添加到一个命名空间,然后从该命名空间按名称加载一个类。
【解决方案3】:

As of typescript 0.9.1,你可以这样做playground

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();

【讨论】:

  • 我只是想让你知道,我已经在互联网上搜索了近 4 个小时来解决这个问题。你的答案是唯一对我有用的答案。我不敢相信这会如此晦涩难懂,我认为多态子类的动态加载会更常见。
  • 已经很久了,我什至不记得回答这个或它做了什么......确定接受的答案对你没有帮助@user2130130?无论如何,我很高兴我帮助了某人,呵呵 :)
  • 不可能!恕我直言,您的答案是唯一有价值的答案。所有其他的要么涉及取消引用全局对象。你的是唯一一个让我得到一个类的类型。由于类实际上只是 JS 中的构造函数,因此这是在 vanilla 中非常容易而在 TS 中令人讨厌的困难的事情之一。用例:根据序列化对象中包含的某种类代码,接收序列化对象并将它们转换为使用不同类的全功能类实例。对于很多应用程序来说绝对必要。帖子应该是@top。
  • 我只有字符串而不是类名。示例:let dummy='className'; createHandler(dummy) - 不会因为 dummy 是字符串而工作
【解决方案4】:

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

import * as handlers from './handler';

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

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

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

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

【讨论】:

  • 我终于找到了适合我的正确答案。谢谢!
  • 太棒了!谢谢你。我花了几个小时寻找消失类型的解决方案。这适用于 3.0.3 ES7
  • 这就是我需要的!谢谢!
【解决方案5】:

更新

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

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

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

更新:没有命名空间使用new (&lt;any&gt;window)[classname]()

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

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

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

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

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

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

【讨论】:

  • 应该是答案 ;-)
  • 如果您不使用命名空间怎么办?是否有默认命名空间可供使用?
  • @Learner:非常好的问题。将其添加到答案中。只需使用 new window[classname]() 按名称构造一个非命名空间的 TypeScript 类(假设您在默认命名空间中运行代码)。
  • 非常感谢。在我的项目中,我没有明确声明 typescript 命名空间,而是从 index.ts 文件中导出类型。因此,我能够使用此解决方案执行以下操作。 import * as MyNamespace from '/someFileWithExports' 然后new MyNamespace[classNameString]
  • 感谢好东西!
【解决方案6】:

另一种方法是动态调用文件并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');

【讨论】:

    【解决方案7】:
    function fromCamelCase(str: string) {
      return str
        // insert a '-' between lower & upper
        .replace(/([a-z])([A-Z])/g, '$1-$2').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]();
      }
    

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

    【讨论】:

      【解决方案8】:

      我找到了另一种方法,因为我无法访问窗口。

      要创建的示例类:

      class MyService {
      
        private someText: string;
      
        constructor(someText: string) {
          this.someText = someText;
        }
      
        public writeSomeText() {
          console.log(this.someText);
        }
      }
      

      工厂类:

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

      然后使用工厂创建实例:

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

      【讨论】:

      • 与乔的回答一样 - OP 正在寻找基于输入字符串的创建。这意味着 OP 正在寻找您的示例中的呼叫:factory.createService("MyService", 'Hello World');
      【解决方案9】:

      在某些特殊情况下,使用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());
      

      注意:如果不是必需的,则不应使用eval,因为它将以调用者的权限执行字符串中包含的代码。见:eval() - JavaScript | MDN

      在安全的情况下,当您知道代码字符串的来源和作用时(尤其是当您知道它不是来自用户输入时),您可以使用它。在上面描述的案例中,我们使用我们对 TypeScript 类名及其包的了解来创建一个新实例。

      【讨论】:

        猜你喜欢
        • 2021-12-29
        • 2020-05-21
        • 1970-01-01
        • 1970-01-01
        • 2019-11-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-12-19
        相关资源
        最近更新 更多