依赖注入库 - 重命名注入值



我想按名称注入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 框架所做的工作不仅仅是 require('x')...它将首先尝试注入其他值,如果没有其他值存在,那么它将尝试需要该值。

另请注意,这里的要求是,当您调用val.toString()时,"lodash"将被视为参数名称。但是 _ 而不是 lodash 会在函数体内部的运行时看到。这是因为为了注入 lodash,我们调用 fn.toString() 来获取参数名称。

更新

这是指向 npm 包的链接di-proxy(受此答案的启发),具有 100% 的代码覆盖率,并支持记忆以提高性能,与 Node 兼容.js>=6.0.0.

旧答案

这是我在修改对象解构和Proxy时想出的一个很棒的解决方案:

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

工作原理

通过对象解构将参数传递给函数会为对象文本上的每个属性调用 getter 方法,以便在执行函数时确定值。

如果被解构的对象初始化为Proxy,则可以使用对尝试解析的属性名称的引用来拦截每个 getter 调用,并返回您选择用来解析它的值。在这种情况下,分辨率应该是require(name),只需在函数对象参数中将其指定为属性名称即可注入模块。

下面是一个演示的链接,您可以在其中实际看到它在 Node.js 中工作。

在线试用!

下面是该演示中的代码仅供参考,因为它在更大程度上演示了对象解构:

/* MIT License */
/* Copyright 2017 Patrick Roberts */
// dependency injection utility
function inject(callbackfn) {
const handler = {
get(target, name) {
return require(name);
}
};
const proxy = new Proxy({}, handler);
return (...args) => callbackfn.call(this, proxy, ...args);
}
// usage
// wrap function declaration with inject()
const val = inject(function ({
fs: { readFile: fsRead, writeFile: fsWrite },
child_process: { fork: cpF, spawn: cpS, exec: cpE },
events: { EventEmitter }
}, other, args) {
// already have access to modules, no need to require() here
console.log('fs:', { fsRead, fsWrite });
console.log('child_process:', { fork: cpF, spawn: cpS, exec: cpE });
console.log('EventEmitter:', EventEmitter);
console.log(other, args);
});
// execute wrapped function with automatic injection
val('other', 'args');

如上所述,我已经发布了一个完整的 npm 包来实现这个概念。如果您喜欢此语法并希望比这个非常基本的示例更具性能和测试,我建议您检查一下。

JavaScript 中没有支持这种映射的语法。即使自定义函数签名解析器被编写为像function({lodash:_}) ...这样的解构参数提供所需的行为,它也会为转译的函数失败,这是一个主要缺陷。处理此问题的最直接方法是

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

而且它显然不适用于像lodash.pick这样的无效变量名。

DI 配方执行此操作的常见做法是提供注释。所有描述的注释都可以组合在一起。它们特别在Angular DI中实现。角喷油器可作为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));

这种风格的注释倾向于使用函数定义,因为为了方便起见,提升允许将注释放置在函数签名上方。

数组注释

函数签名和依赖项列表不必匹配。这个配方也可以在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));

打字稿类型注释

这个配方可以在 Angular(2 及更高版本)中看到,并且依赖于 TypeScript 类型。类型可以从构造函数签名中提取并用于 DI。使之成为可能的东西Reflect元数据提案和 TypeScript 自己的emitDecoratorMetadata功能。

发出的构造函数类型存储为相应类的元数据,可以使用 API 检索Reflect以解析依赖项。这是基于类的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代码,但会产生类型问题,因为Lodash对象不是Dep令牌类的实例。此方法主要对注入到类中的类依赖项有效。

对于非类 DI,需要回退到其他注释。

我做了一些可能对你有用的事情, 但是您始终可以更改它并使用一般想法。

它是用 ES6 功能编写的,但您可以轻松删除它们。

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" 传入这些参数:

di("_", lodash, callback);

现在在回调代码中,您可以使用"_"引用"lodash">

鉴于答案,我仍然认为 Angular 1.x(和 RequireJS)所做的是性能最高的,尽管可能不是最容易使用的:

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

}]);

最新更新