簡體   English   中英

動態加載打字稿類(打字稿的反射)

[英]Dynamically loading a typescript class (reflection for 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)
}

你可以試試:

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

編輯此版本正在使用 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);

當您使用 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());

目前,我們有一個演員表: <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');

更新

要使其在最新的 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 (<any>window)[classname]()

在 TypeScript 中,如果你在命名空間之外聲明一個類,它會為“類函數”生成一個 var。 這意味着它是針對當前范圍存儲的(很可能是window除非您在另一個范圍內運行它,例如 nodejs)。 這意味着你可以只做new (<any>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();

這適用於帶有 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 模塊無法動態加載。

作為打字稿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();

另一種方法是動態調用文件並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');

我找到了另一種方法,因為在我的情況下我無法訪問 window.open 。

要創建的示例類:

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

我正在使用typescript ~2.5.3而且我能夠這樣做:

class AEmailNotification implements IJobStateEmailNotification {}
class ClientJobRequestNotification extends AEmailNotification {}
class ClientJobRequestAcceptedNotification extends AEmailNotification {}
class ClientJobRequestDeclinedNotification extends AEmailNotification {}
class ClientJobRequestCounterOfferNotification extends AEmailNotification {}
class ClientJobRequestEscrowedFundsNotification extends AEmailNotification {}
class ClientJobRequestCommenceNotification extends AEmailNotification {}

export function notificationEmail(action: string) {
    console.log(`+ build factory object for action: ${action}`)

    const actions = {}

    actions['Create job'] = ClientJobRequestNotification
    actions['Accept terms'] = ClientJobRequestAcceptedNotification
    actions['Decline terms'] = ClientJobRequestDeclinedNotification
    actions['Counter offer'] = ClientJobRequestCounterOfferNotification
    actions['Add funds to escrow'] = ClientJobRequestEscrowedFundsNotification
    actions['-- provider to commence the job --'] = ClientJobRequestCommenceNotification

    const jobAction = actions[action]

    if (!jobAction) {
        console.log(`! unknown action type: ${action}`)
        return undefined
    }

    return new jobAction()
}

在某些特殊情況下,使用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 類名及其包的知識來創建一個新實例。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM