Bibliothèque d’dependency injection – renommer les valeurs injectées

Je voudrais injecter Lodash par nom, quelque chose comme ça

let val = function(lodash){ // lodash will be injected, simply by using require('lodash'); }; 

mais disons que je veux renommer l’importation, je veux faire quelque chose comme ceci:

 let val = function({lodash:_}){ }; 

ou

 let val = function(lodash as _){ }; 

Y a-t-il un moyen de faire cela avec ES6 / ES7 / ES8 ou TypeScript?

Notez que cette structure DI fait plus de travail que simplement exiger (‘x’) … elle essaiera d’injecter d’autres valeurs d’abord, si rien d’autre n’existe, elle essaiera d’exiger cette valeur.

Notez également que les exigences ici sont que lorsque vous appelez val.toSsortingng (), alors “lodash” sera considéré comme le nom de l’argument. Mais _ au lieu de lodash serait visible à l’exécution dans le corps de la fonction. En effet, pour injecter lodash, nous appelons fn.toSsortingng () pour obtenir les noms des arguments.

Mettre à jour

Voici un lien vers le paquet npm di-proxy (inspiré de cette réponse) avec une couverture de code à 100%, et une prise en charge de la mémorisation pour augmenter les performances, compatible avec Node.js >=6.0.0 .

Ancienne réponse

Voici une solution géniale que j’ai trouvée en bricolant avec la déstructuration des objects et le Proxy :

 /* MIT License */ /* Copyright 2017 Pasortingck Roberts */ // dependency injection utility function inject(callbackfn) { const handler = { get(target, name) { /* this is just a demo, swap these two lines for actual injection */ // return require(name); return { name }; } }; const proxy = new Proxy({}, handler); return (...args) => callbackfn.call(this, proxy, ...args); } // usage // wrap function declaration with inject() const val = inject(function ({ lodash: _, 'socket.io': sio, jquery: $, express, fs }, other, args) { // already have access to lodash, no need to even require() here console.log(_); console.log(sio); console.log($); console.log(express); console.log(fs); console.log(other, args); }); // execute wrapped function with automatic injection val('other', 'args'); 
 .as-console-wrapper { max-height: 100% !important; } 

Aucune syntaxe en JavaScript ne prend en charge un tel mappage. Même si un parsingur de signature de fonction personnalisé était écrit pour fournir le comportement souhaité aux parameters déstructurés tels que function({lodash:_}) ... , il échouerait pour les fonctions transpilées, ce qui constitue un défaut majeur. La façon la plus simple de gérer cela est

 function foo(lodash){ const _ = lodash; ... } 

Et cela ne fonctionnera évidemment pas pour les noms de variables non valides tels que lodash.pick .

Pour ce faire, une pratique courante des DI consiste à fournir des annotations . Toutes les annotations décrites peuvent être combinées. Ils sont particulièrement implémentés dans DI angular . L’injecteur angular est disponible pour une utilisation autonome (y compris Node) en tant que bibliothèque injection-js .

Propriété d’annotation

De cette façon, la signature de la fonction et la liste des dépendances ne doivent pas nécessairement correspondre. Cette recette peut être vue en action dans AngularJS.

La propriété contient une liste de jetons DI. Ils peuvent être des noms de dépendances qui seront chargés avec require ou quelque chose d’autre.

 // may be more convenient when it's a ssortingng const ANNOTATION = Symbol(); ... foo[ANNOTATION] = ['lodash']; function foo(_) { ... } bar[ANNOTATION] = ['lodash']; function bar() { // doesn't need a param in signature const _ = arguments[0]; ... } 

Et DI est réalisée comme

 const fnArgs = require('fn-args'); const annotation = foo[ANNOTATION] || fnArgs(foo); foo(...annotation.map(depName => require(depName)); 

Ce style d’annotations permet d’utiliser les définitions de fonction, car le levage permet de placer des annotations au-dessus de la signature de fonction pour plus de commodité.

Annotation de tableau

La signature de fonction et la liste des dépendances ne doivent pas nécessairement correspondre. Cette recette est également visible dans AngularJS.

Lorsque la fonction est représentée sous la forme d’un tableau, cela signifie qu’il s’agit d’une fonction annotée et que ses parameters doivent être traités comme des annotations, le dernier étant la fonction elle-même.

 const foo = [ 'lodash', function foo(_) { ... } ]; ... const fn = foo[foo.length - 1]; const annotation = foo.slice(0, foo.length - 1); foo(...annotation.map(depName => require(depName)); 

Annotation de type TypeScript

Cette recette peut être vue dans Angular (2 et plus) et repose sur des types TypeScript. Les types peuvent être extraits de la signature du constructeur et utilisés pour DI. Ce qui le rend possible est la proposition de métadonnées Reflect et la propre fonction emitDecoratorMetadata TypeScript.

Les types de constructeur émis sont stockés en tant que métadonnées pour les classes respectives et peuvent être récupérés avec l’API Reflect pour résoudre les dépendances. Ceci est une DI basée sur les classes, puisque les décorateurs ne sont supportés que par les classes, cela fonctionne mieux avec les conteneurs DI:

 import 'core-js/es7/reflect'; abstract class Dep {} function di(target) { /* can be noop to emit metadata */ } @di class Foo { constructor(dep: Dep) { ... } } ... const diContainer = { Dep: require('lodash') }; const annotations = Reflect.getMetadata('design:paramtypes', Foo); new (Foo.bind(Foo, ...annotations.map(dep => diContainer [dep]))(); 

Cela produira du code JS utilisable, mais créera des problèmes de type, car l’object Lodash n’est pas une instance de la classe de jeton Dep . Cette méthode est principalement efficace pour les dépendances de classe injectées dans les classes.

Pour les ID sans classe, un recours à d’autres annotations est requirejs.

J’ai fait quelque chose qui pourrait fonctionner pour vous, mais vous pouvez toujours le changer et utiliser l’idée générale.

Il est écrit avec les fonctionnalités de l’ES6, mais vous pouvez facilement les supprimer.

 let di = function() { const argumentsLength = arguments.length; //you must call this func with at least a callback if (argumentsLength === 0) return; //this will be called with odd amount of variables, //pairs of key and assignment, and the callback //means: 1,3,5,7.... amount of args if (argumentsLength%2 === 0) throw "mismatch of args"; //here we will assing the variables to "this" for (key in arguments) { //skip the callback if(key===argumentsLength-1) continue; //skip the "key", it will be used in the next round if(key%2===0) continue; const keyToSet = arguments[key-1]; const valToSet = arguments[key]; this[keyToSet] = valToSet; } arguments[argumentsLength-1].apply(this); } di("name", {a:"IwillBeName"}, "whatever", "IwillBeWhatever", () => { console.log(whatever); console.log(name); }); 

en dernière ligne, vous appelez la fonction “di” passer dans ces arguments:

 di("_", lodash, callback); 

maintenant dans votre code de rappel, vous pouvez référencer “lodash” avec “_”

Étant donné les réponses, je pense toujours que Angular 1.x (et RequireJS) est le plus performant, bien que peut-être pas le plus simple à utiliser:

 let = createSomething('id', ['lodash', function(_){ }]);