javascript - Dynamisk lasting av en typescript-klasse (refleksjon for typescript)
Jeg ønsker å kunne instansiere en TypeScript-klasse hvor jeg får klasse- og konstruktørdetaljer under kjøring. Funksjonen jeg ønsker å skrive vil ta inn klassenavnet og konstruktørparametrene.
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)
}
Svar #1
Du kunne prøve:
var newInstance = Object.create(window[className].prototype);
newInstance.constructor.apply(newInstance, instanceparameters);
return newInstance;
Rediger Denne versjonen fungerer ved bruk av TypeScript-lekeplassen, med eksemplet:
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);
Svar #2
Siden du bruker TypeScript antar jeg at du vil at det innlastede objektet skal være typet. Så her er eksempelklassen (og et grensesnitt fordi du velger å laste en av mange implementasjoner, for eksempel).
interface IExample {
test() : string;
}
class Example {
constructor (private a: string, private b: string) {
}
test() {
return this.a + ' ' + this.b;
}
}
Så du ville brukt en slags loader for å gi deg tilbake en implementasjon:
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;
}
}
Og deretter last det slik:
var loader = new InstanceLoader(window);
var example = <IExample> loader.getInstance('Example', 'A', 'B');
alert(example.test());
Akkurat nå har vi en casting: <IExample> - men når generics blir lagt til, kan vi kvitte oss med dette og bruke generics i stedet. Det vil se slik ut (med tanke på at det ennå ikke er en del av språket!)
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');
Svar #3
Oppdater
For å få dette til å fungere i nyeste TypeScript, må du nå kaste navnerommet til any. Ellers får du en Error TS7017 Build:Element har implisitt en 'any'-type fordi typen '{}' ikke har noen indeksignatur.
Hvis du har et spesifikt navnerom/modul, for alle klassene du ønsker å lage, kan du enkelt gjøre dette:
var newClass: any = new (<any>MyNamespace)[classNameString](parametersIfAny);
Oppdatering: Uten et navnerom bruk new (<any>window)[classname]()
I TypeScript, hvis du erklærer en klasse utenfor et namespace, genererer det en var for «klassefunksjonen». Det betyr at den lagres mot det gjeldende omfanget (mest sannsynlig window med mindre du kjører det under et annet omfang, f.eks. som nodejs). Det betyr at du bare kan gjøre new (<any>window)[classNameString]:
Dette er et fungerende eksempel (all kode, ingen namespace):
class TestClass
{
public DoIt()
{
alert("Hello");
}
}
var test = new (<any>window)["TestClass"]();
test.DoIt();
For å se hvorfor det fungerer, ser den genererte JS-koden slik ut:
var TestClass = (function () {
function TestClass() {
}
TestClass.prototype.DoIt = function () {
alert("Hello");
};
return TestClass;
}());
var test = new window["TestClass"]();
test.DoIt();
Svar #4
Dette fungerer i TypeScript 1.8 med ES6-modul:
import * as handlers from './handler';
function createInstance(className: string, ...args: any[]) {
return new (<any>handlers)[className](...args);
}
Klasser eksporteres i handler-modulen. De kan eksporteres på nytt fra andre moduler.
export myClass {};
export classA from './a';
export classB from './b';
Når det gjelder å sende modulnavn i argumenter, kan jeg ikke få det til å fungere fordi ES6-modulen ikke kan lastes dynamisk.
