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

时间:2017-06-18 19:34:20

标签: javascript node.js typescript dependency-injection ecmascript-next

我想通过名字注入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()来获取参数名称。

4 个答案:

答案 0 :(得分:5)

更新

以下是npm package di-proxy的链接(受此回答启发),100%代码覆盖率,并支持memoization以提高性能,与Node.js >=6.0.0兼容。

旧答案

这是我在使用object destructuringProxy进行修补时想出的一个很棒的解决方案:

/* 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中工作。

Try it online!

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

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

TypeScript类型注释

此配方可以在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(_){


}]);