简体   繁体   English

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

[英]Dependency injection library - renaming injected values

I'd like to inject lodash by name, something like this: 我想通过名字注入lodash,如下所示:

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

but say I want to rename the import, I want do something like this: 但是说我要重命名导入,我想做这样的事情:

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

};

or 要么

let val = function(lodash as _){

};

is there a way to do this with either ES6/ES7/ES8 or TypeScript? 有没有办法用ES6 / ES7 / ES8或TypeScript做到这一点?

Note that this DI framework does more work than just require('x')...it will try to inject other values first, if nothing else exists, then it will attempt to require the value. 请注意,这个DI框架比仅需要('x')做更多的工作......它将首先尝试注入其他值,如果没有其他值,那么它将尝试要求该值。

Note also that the requirements here are that when you call val.toString() then "lodash" would be seen as the argument name. 另请注意,这里的要求是当您调用val.toString()时,“lodash”将被视为参数名称。 But _ instead of lodash would be seen at runtime inside the function body. 但是在函数体内的运行时会看到_而不是lodash。 This is because in order to inject lodash, we call fn.toString() to get the argument names. 这是因为为了注入lodash,我们调用fn.toString()来获取参数名称。

Update 更新

Here's a link to the npm package di-proxy (inspired by this answer) with 100% code coverage, and support for memoization to increase performance, compatible with Node.js >=6.0.0 . 这里是npm包di-proxy的链接(灵感来自这个答案),具有100%的代码覆盖率,并支持memoization以提高性能,与Node.js >=6.0.0兼容。

Old answer 老答案

Here's an awesome solution I figured out while tinkering around with object destructuring and Proxy : 这是一个很棒的解决方案,我在修改对象解构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; } 

How it works 这个怎么运作

Passing parameters to a function via object destructuring invokes the getter methods for each property on the object literal in order to determine the values when the function is executed. 通过对象解构将参数传递给函数会调用对象文字上每个属性的getter方法,以便在执行函数时确定值。

If the object being destructured is initialized as a Proxy , you can intercept each getter invocation with a reference to the property name attempting to be resolved, and return a value you choose to resolve it with. 如果要解析的对象初始化为Proxy ,则可以使用对尝试解析的属性名称的引用来拦截每个getter调用 ,并返回您选择使用的值来解析它。 In this case, the resolution should be the require(name) , which injects the module just by specifying it as a property name in the function object parameter. 在这种情况下,分辨率应该是require(name) ,它只是通过在函数对象参数中将其指定为属性名称来注入模块。

Below is a link to a demo where you can actually see it working in Node.js. 下面是一个演示的链接,您可以在其中实际看到它在Node.js中工作。

Try it online! 在线尝试!

Here's the code in that demo just for reference, because it demonstrates object destructuring to a larger degree: 这是该演示中的代码仅供参考,因为它在更大程度上演示了对象解构:

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

As stated above, I have published a full npm package implementing this concept. 如上所述,我已经发布了一个完整的npm包来实现这个概念。 I recommend you check it out if you like this syntax and want something a little more performant and tested than this very basic example. 如果您喜欢这种语法,我建议您查看它,并希望比这个非常基本的示例更具性能和测试性。

There's no syntax in JavaScript that supports such mapping. JavaScript中没有支持这种映射的语法。 Even if custom function signature parser were written to to provide desired behaviour for destructured params like function({lodash:_}) ... , it would fail for transpiled functions, which is a major flaw. 即使编写了自定义函数签名解析器来为function({lodash:_}) ...等解构的参数提供所需的行为,它也会因为转换函数而失败,这是一个主要缺陷。 The most straightforward way to handle this is 处理这个问题最直接的方法是

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

And it obviously won't work for invalid variable names like lodash.pick . 它显然不适用于无效的变量名称,如lodash.pick

A common practice for DI recipes to do this is to provide annotations . DI配方的常见做法是提供注释 All of described annotations can be combined together. 所有描述的注释可以组合在一起。 They are particularly implemented in Angular DI . 它们特别在Angular DI中实现。 Angular injector is available for standalone use (including Node) as injection-js library. Angular进样器可作为injection-js库独立使用(包括Node)。

Annotation property 注释属性

This way function signature and the list of dependencies don't have to match. 这样,函数签名和依赖项列表不必匹配。 This recipe can be seen in action in AngularJS. 这个配方可以在AngularJS中看到。

The property contains a list of DI tokens. 该属性包含DI令牌列表。 They can be names of dependencies that will be loaded with require or something else. 它们可以是将要加载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];
  ...
}

And DI is performed like DI就像执行一样

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

This style of annotations disposes to make use of function definitions, because hoisting allows to place annotation above function signature for convenience. 这种注释方式用于利用函数定义,因为提升允许将注释放在函数签名之上以方便使用。

Array annotation 数组注释

Function signature and the list of dependencies don't have to match. 函数签名和依赖项列表不必匹配。 This recipe can be seen in AngularJS, too. 这个配方也可以在AngularJS中看到。

When function is represented as an array, this means that it is annotated function, and its parameters should be treated as annotations, and the last one is function itself. 当函数表示为数组时,这意味着它是带注释的函数,其参数应该被视为注释,最后一个是函数本身。

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 type annotation TypeScript类型注释

This recipe can be seen in Angular (2 and higher) and relies on TypeScript types. 此配方可以在Angular(2和更高版本)中看到,并依赖于TypeScript类型。 Types can be extracted from constructor signature and used for DI. 类型可以从构造函数签名中提取并用于DI。 Things that make it possible are Reflect metadata proposal and TypeScript's own emitDecoratorMetadata feature . 使其成为可能的是Reflect元数据提议和TypeScript自己的emitDecoratorMetadata功能

Emitted constructor types are stored as metadata for respective classes and can be retrieved with Reflect API to resolve dependencies. 发出的构造函数类型存储为各个类的元数据,可以使用Reflect API检索以解析依赖项。 This is class-based DI, since decorators are supported only on classes, it works best with DI containers: 这是基于类的 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]))();

This will produce workable JS code but will create type issues, because Lodash object isn't an instance of Dep token class. 这将产生可行的JS代码,但会产生类型问题,因为Lodash对象不是Dep令牌类的实例。 This method is primarily effective for class dependencies that are injected into classes. 此方法主要对注入类的类依赖性有效。

For non-class DI a fallback to other annotations is required. 对于非类DI,需要回退到其他注释。

I have did something that may work for you, but you can always change it and use the general idea. 我已经做了一些可能适合你的事情,但是你总是可以改变它并使用一般的想法。

It is written with ES6 features, but you can easily remove them. 它使用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);
});

in the bottom line, you call the func "di" pass in these args: 在底线,你在这些args中调用func“di”传递:

di("_", lodash, callback);

now inside you callback code, you could reference "lodash" with "_" 现在在你回调代码中,你可以用“_”引用“lodash”

Given the answers, I still think what Angular 1.x (and RequireJS) does is the most performant, although perhaps not easiest to use: 鉴于答案,我仍然认为Angular 1.x(和RequireJS)所做的是性能最高的,尽管可能不是最容易使用的:

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


}]);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM