dependency injection库 – 重命名注入的值
我想按名称注入lodash,如下所示:
let val = function(lodash){ // lodash will be injected, simply by using require('lodash'); };
但是说我想重命名导入,我想要做这样的事情:
let val = function({lodash:_}){ };
要么
let val = function(lodash as _){ };
有没有办法做到这一点与ES6 / ES7 / ES8或TypeScript?
请注意,这个DI框架比只需要('x')做更多的工作…它将尝试注入其他值首先,如果没有其他的存在,那么它将尝试要求的价值。
还要注意,这里的要求是,当你调用val.toString()时,“lodash”将被视为参数名称。 但是在函数体内的运行时会看到_而不是lodash。 这是因为为了注入lodash,我们调用fn.toString()来获取参数名称。
更新
这里有一个指向npm包di-proxy
的链接(灵感来自于这个答案),代码覆盖率达到100%,支持memoization以提高性能,与Node.js >=6.0.0
兼容。
老答案
我在解决对象解构和Proxy
的时候,发现了一个很棒的解决scheme:
/* MIT License */ /* Copyright 2017 Patrick 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; }
JavaScript中没有支持这种映射的语法。 即使自定义函数签名parsing器被写入来为function({lodash:_}) ...
等解构参数提供期望的行为,它也将失败,这是一个主要的缺陷。 处理这个最直接的方法是
function foo(lodash){ const _ = lodash; ... }
它显然不适用于像lodash.pick
这样的无效variables名称。
DI食谱做这个的一个惯例是提供注释 。 所有描述的注释可以组合在一起。 它们在angular度DI中特别实施。 Angular注入器可以独立使用(包括Node)作为injection-js
库。
注释属性
这样函数签名和依赖关系列表就不需要匹配。 这个配方可以在AngularJS中看到。
该属性包含一个DI令牌列表。 它们可以是将被加载require
或其他东西的依赖项的名称。
// may be more convenient when it's a string const ANNOTATION = Symbol(); ... foo[ANNOTATION] = ['lodash']; function foo(_) { ... } bar[ANNOTATION] = ['lodash']; function bar() { // doesn't need a param in signature const _ = arguments[0]; ... }
而且DI是这样执行的
const fnArgs = require('fn-args'); const annotation = foo[ANNOTATION] || fnArgs(foo); foo(...annotation.map(depName => require(depName));
这种注释configuration是为了方便使用函数定义,因为提供允许在函数签名之上放置注释。
数组注释
函数签名和依赖关系列表不必匹配。 这个配方也可以在AngularJS中看到。
当函数被表示为一个数组时,这意味着它是注释函数,其参数应被视为注释,最后一个函数本身。
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));
TypeScripttypes注释
这个配方可以在Angular(2和更高版本)中看到,并依赖于TypeScripttypes。 types可以从构造函数签名中提取并用于DI。 使之成为可能的是Reflect
元数据提议和TypeScript自己的emitDecoratorMetadata
function 。
释放的构造函数types作为相应类的元数据存储,并可以通过Reflect
API进行检索以解决依赖关系。 这是基于类的 DI,因为装饰器仅在类上受支持,所以对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]))();
这将产生可行的JS代码,但会创buildtypes问题,因为Lodash对象不是Dep
令牌类的实例。 此方法主要对注入到类中的类相关性有效。
对于非课堂DI,需要回退到其他注释。
我已经做了一些可能适合你的事情,但你可以随时改变它,并使用一般的想法。
它是用ES6function编写的,但可以轻松删除它们。
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); });
在底线,你可以在这些参数中调用func“di”pass:
di("_", lodash, callback);
现在在你callback代码里面,你可以用“_”来引用“lodash”
给出答案,我仍然认为Angular 1.x(和RequireJS)所做的是最高性能的,尽pipe可能不是最简单的:
let = createSomething('id', ['lodash', function(_){ }]);