Chargement dynamique d'une classe dactylographiée (réflexion pour typecript)

J'aimerais pouvoir créer une instance de script dactylographié où je reçois les détails de la classe et du constructeur au moment de l'exécution. La fonction que j'aimerais écrire prendra le nom de la classe et les paramètres du constructeur.

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

Tu pourrais essayer:

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

Modifier Cette version fonctionne à l'aide du terrain de jeu TypeScript, à l'aide de l'exemple:

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

Comme vous utilisez TypeScript, je suppose que vous voulez que l'objet chargé soit tapé. Voici donc la classe d'exemple (et une interface parce que vous choisissez de charger une des nombreuses implémentations, par exemple).

 interface IExample { test() : string; } class Example { constructor (private a: string, private b: string) { } test() { return this.a + ' ' + this.b; } } 

Donc, vous utiliserez un type de chargeur pour vous rendre une implémentation:

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

Et puis, chargez-le comme ceci:

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

Pour le moment, nous avons un casting: <IExample> – mais lorsque des génériques sont ajoutés, nous pourrions supprimer cela et utiliser des génériques à la place. Cela ressemblera à ceci (en gardant à l'esprit qu'il ne fait pas partie de la langue pour le moment!)

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

Si vous avez un espace de noms / module spécifique, pour toutes les classes que vous souhaitez créer, vous pouvez simplement le faire:

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

Mise à jour: sans espace de nom, utilisez une new window[classname]()

Dans TypeScript, si vous déclarez une classe en dehors d'un espace de noms, cela génère un var pour la "fonction de classe". Cela signifie qu'il est stocké contre la portée actuelle (la window plus probable window sauf si vous l'exécutez sous une autre portée, par exemple, comme nodejs). Cela signifie que vous pouvez simplement new window[classNameString] :

Ceci est un exemple de travail (tout code, pas d'espace de noms):

 class TestClass { public DoIt() { alert("Hello"); } } var test = new window["TestClass"](); test.DoIt(); 

Pour voir pourquoi cela fonctionne, le code JS généré ressemble à ceci:

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

Cela fonctionne dans TypeScript 1.8 avec le module ES6:

 import * as handlers from './handler'; function createInstance(className: string, ...args: any[]) { return new (<any>handlers)[className](...args); } 

Les cours sont exportés dans le module de handler . Ils peuvent être réexportés à partir d'autres modules.

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

En ce qui concerne le passage du nom du module dans les conflits, je ne peux pas le faire fonctionner car le module ES6 ne peut pas être chargé dynamiquement.

À partir du type 0.9.1 , vous pouvez faire quelque chose comme cette aire de jeux :

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