我想通过名字注入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()来获取参数名称。
答案 0 :(得分:5)
以下是npm package di-proxy
的链接(受此回答启发),100%代码覆盖率,并支持memoization以提高性能,与Node.js >=6.0.0
兼容。
这是我在使用object destructuring和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
,您可以intercept each getter invocation尝试解析属性名称的引用,并返回您选择使用的值来解析它。在这种情况下,分辨率应该是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包来实现这个概念。如果您喜欢这种语法,我建议您查看它,并希望比这个非常基本的示例更具性能和测试性。
答案 1 :(得分:3)
JavaScript中没有支持此类映射的语法。即使编写了自定义函数签名解析器来为像function({lodash:_}) ...
这样的结构化参数提供所需的行为,它也会因转换函数而失败,这是一个主要缺陷。处理此问题最直接的方法是
function foo(lodash){
const _ = lodash;
...
}
显然,它不适用于lodash.pick
等无效变量名称。
DI配方的常见做法是提供注释。所有描述的注释可以组合在一起。它们特别在Angular DI中实现。 Angular进样器可作为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
metadata proposal和TypeScript自己的emitDecoratorMetadata
feature。
发出的构造函数类型存储为各个类的元数据,可以使用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代码但会产生类型问题,因为Lodash对象不是Dep
令牌类的实例。此方法主要对注入类的类依赖性有效。
对于非类DI,需要回退到其他注释。
答案 2 :(得分:0)
我做了一些可能适合你的事情, 但是你总是可以改变它并使用一般的想法。
它是用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” 传递这些args:
di("_", lodash, callback);
现在在你的回调代码中,你可以用“_”
来引用“lodash”答案 3 :(得分:0)
鉴于答案,我仍然认为Angular 1.x(和RequireJS)所做的是性能最高的,尽管可能不是最容易使用的:
let = createSomething('id', ['lodash', function(_){
}]);