TypeScript 中的依赖注入

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16069416/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-11 01:03:25  来源:igfitidea点击:

Dependency injection in TypeScript

tddrequirejstypescriptamd

提问by Sop Killen

I'm looking into the possibilities to do TDD with TypeScript. If I write my tests in TypeScript, is it possible to make the import statements return mocks for my class under test? Or is the only feasible approach to write the tests in pure javascript and deal with injecting AMDs myself?

我正在研究使用 TypeScript 进行 TDD 的可能性。如果我用 TypeScript 编写测试,是否可以让 import 语句为我的测试类返回模拟?或者是用纯 javascript 编写测试并自己处理注入 AMD 的唯一可行方法?

回答by Remo H. Jansen

I have developed an IoC container called InversifyJS with advanced dependency injection features like contextual bindings.

我开发了一个名为 InversifyJS 的 IoC 容器,具有高级依赖注入功能,如上下文绑定。

You need to follow 3 basic stepsto use it:

您需要遵循3 个基本步骤来使用它:

1. Add annotations

1. 添加注释

The annotation API is based on Angular 2.0:

注解 API 基于 Angular 2.0:

import { injectable, inject } from "inversify";

@injectable()
class Katana implements IKatana {
    public hit() {
        return "cut!";
    }
}

@injectable()
class Shuriken implements IShuriken {
    public throw() {
        return "hit!";
    }
}

@injectable()
class Ninja implements INinja {

    private _katana: IKatana;
    private _shuriken: IShuriken;

    public constructor(
        @inject("IKatana") katana: IKatana,
        @inject("IShuriken") shuriken: IShuriken
    ) {
        this._katana = katana;
        this._shuriken = shuriken;
    }

    public fight() { return this._katana.hit(); };
    public sneak() { return this._shuriken.throw(); };

}

2. Declare bindings

2. 声明绑定

The binding API is based on Ninject:

绑定 API 基于 Ninject:

import { Kernel } from "inversify";

import { Ninja } from "./entities/ninja";
import { Katana } from "./entities/katana";
import { Shuriken} from "./entities/shuriken";

var kernel = new Kernel();
kernel.bind<INinja>("INinja").to(Ninja);
kernel.bind<IKatana>("IKatana").to(Katana);
kernel.bind<IShuriken>("IShuriken").to(Shuriken);

export default kernel;

3. Resolve dependencies

3. 解决依赖

The resolution API is based on Ninject:

解析 API 基于 Ninject:

import kernel = from "./inversify.config";

var ninja = kernel.get<INinja>("INinja");

expect(ninja.fight()).eql("cut!"); // true
expect(ninja.sneak()).eql("hit!"); // true

The latest release (2.0.0) supports many use cases:

最新版本 (2.0.0) 支持许多用例:

  • Kernel modules
  • Kernel middleware
  • Use classes, string literals or Symbols as dependency identifiers
  • Injection of constant values
  • Injection of class constructors
  • Injection of factories
  • Auto factory
  • Injection of providers (async factory)
  • Activation handlers (used to inject proxies)
  • Multi injections
  • Tagged bindings
  • Custom tag decorators
  • Named bindings
  • Contextual bindings
  • Friendly exceptions (e.g. Circular dependencies)
  • 内核模块
  • 内核中间件
  • 使用类、字符串文字或符号作为依赖标识符
  • 注入常​​数值
  • 注入类构造函数
  • 工厂注塑
  • 汽车厂
  • 提供程序的注入(异步工厂)
  • 激活处理程序(用于注入代理)
  • 多次注射
  • 标记绑定
  • 自定义标签装饰器
  • 命名绑定
  • 上下文绑定
  • 友好异常(例如循环依赖)

You can learn more about it at https://github.com/inversify/InversifyJS

您可以在https://github.com/inversify/InversifyJS 上了解更多信息

回答by Philip Bulley

I use infuse.jsfor Dependency Injection in TypeScript.

我在 TypeScript 中使用infuse.js进行依赖注入。

Reference the d.ts

参考 d.ts

/// <reference path="definition/infusejs/infusejs.d.ts"/>

Initialize your injector at startup

在启动时初始化您的注射器

this.injector = new infuse.Injector();  

Map Dependencies

映射依赖

this.injector.mapClass( 'TodoController', TodoController );
this.injector.mapClass( 'TodoView', TodoView );
this.injector.mapClass( 'TodoModel', TodoModel, true );  // 'true' Map as singleton

Inject Dependencies

注入依赖

export class TodoController
{
    static inject = ['TodoView', 'TodoModel'];

    constructor( todoView:TodoView, todoModel:TodoModel )
    {

    }
 }

It's string based as opposed to being type based (as reflection isn't yet possible in TypeScript). Despite that, it works very well in my applications.

它是基于字符串的,而不是基于类型的(因为在 TypeScript 中还不能进行反射)。尽管如此,它在我的应用程序中运行良好。

回答by Oleh Dokuka

Try this Dependency Injector (Typejector)

试试这个依赖注入器(Typejector)

GitHub Typejector

GitHub打字机

With new TypeScript 1.5 it is possible using annotation way

使用新的 TypeScript 1.5 可以使用注释方式

For example

例如

    @injection
    class SingletonClass {
        public cat: string = "Kitty";
        public dog: string = "Hot";

        public say() {
            alert(`${this.cat}-Cat and ${this.dog}-Dog`);
        }
    }
    @injection
    class SimpleClass {
        public say(something: string) {
            alert(`You said ${something}?`);
        }
    }

    @resolve
    class NeedInjectionsClass {
        @inject(SingletonClass)
        public helper: SingletonClass;
        @inject(SimpleClass)
        public simpleHelper: SimpleClass;

        constructor() {
            this.helper.say();
            this.simpleHelper.say("wow");
        }
    }
    class ChildClass extends NeedInjectionsClass {

    }

    var needInjection = new ChildClass();

For question case: some property should realise pseudo Interface (or abstract class) like in next example.

对于问题案例:某些属性应该像下一个示例一样实现伪接口(或抽象类)。

    class InterfaceClass {
        public cat: string;
        public dog: string;

        public say() {

        }
    }

    @injection(true, InterfaceClass)
    class SingletonClass extends InterfaceClass {
        public cat: string = "Kitty";
        public dog: string = "Hot";

        public say() {
            alert(`${this.cat}-Cat and ${this.dog}-Dog`);
        }
    }

    @injection(true, InterfaceClass)
    class MockInterfaceClass extends InterfaceClass {
        public cat: string = "Kitty";
        public dog: string = "Hot";

        public say() {
            alert(`Mock-${this.cat}-Cat and Mock-${this.dog}-Dog`);
        }
    }

    @injection
    class SimpleClass {
        public say(something: string) {
            alert(`You said ${something}?`);
        }
    }

    @resolve
    class NeedInjectionsClass {
        @inject(InterfaceClass)
        public helper: InterfaceClass;
        @inject(SimpleClass)
        public simpleHelper: SimpleClass;

        constructor() {
            this.helper.say();
            this.simpleHelper.say("wow");
        }
    }

    class ChildClass extends NeedInjectionsClass {

    }

    var needInjection = new ChildClass();

Note:Mock injection should define after source code, because it mast redefine class-creator for interface

注意:模拟注入应该在源代码之后定义,因为它必须为接口重新定义类创建器

回答by Connor Wyatt

For people who use Angular2 I have developed Fluency Injection https://www.npmjs.com/package/fluency-injection. The documentation is quite complete and it mimics the behaviour of Angular2's DI.

对于使用 Angular2 的人,我开发了 Fluency Injection https://www.npmjs.com/package/fluency-injection。文档非常完整,它模仿了 Angular2 的 DI 的行为。

Feedback is much appreciated and I hope it helps you :)

非常感谢您的反馈,希望对您有所帮助:)

回答by Meirion Hughes

I've been developing a DI solution called Pigly. An example given the original question regarding injecting and testing (admittedly not automatic-mock generation - although you could try ts-auto-mockas I've done here):

我一直在开发一个名为Pigly的 DI 解决方案。给出了有关注入和测试的原始问题的示例(诚然不是自动模拟生成 - 尽管您可以像我在这里所做的那样尝试ts-auto-mock):

Given:

鉴于:

interface IDb {
  set(key: string, value: string);
}

interface IApi {
  setName(name: string);
}

class Api implements IApi {
  constructor(private db: IDb) {}
  setName(name: string){
    this.db.set("name", name);
  }
}

We can bind the types with,

我们可以绑定类型,

import { Kernel, toSelf, to, toConst } from 'pigly';
import * as sinon from 'sinon';

let spy = sinon.spy();

let kernel = new Kernel();

kernel.bind(toSelf(Api));
kernel.bind<IApi>(to<Api>());
kernel.bind<IDb>(toConst({ set: spy }));

then resolve and test with:

然后解决并测试:

let api = kernel.get<IApi>();

api.setName("John");

console.log(spy.calledWith("name", "John"));

execution/compilation of this example requires a typescript transformer - to compile the interface-symbols and constructor provider into plain javascript. There are a few ways to do this. The ts-node + ttypescript approach is to have a tsconfig.json:

这个例子的执行/编译需要一个打字稿转换器 - 将接口符号和构造函数提供者编译成普通的 javascript。有几种方法可以做到这一点。ts-node + ttypescript 方法是有一个 tsconfig.json:

{
  "compilerOptions": {
    "target": "es2015",
    "module": "commonjs",
    "moduleResolution": "node",
    "plugins": [{
      "transform": "@pigly/transformer"
    }]
  }
}

and execute with

并执行

ts-node --compiler ttypescript example-mock.ts

Pigly has the distinction of not requiring any changes to your (or third-party) classes, at the expense of either the use of a typescript transformer, or more verbose binding if (you don't want to use the transformer). Its still experimental, but I think it shows promise.

Pigly 的区别在于不需要对您的(或第三方)类进行任何更改,代价是使用 typescript 转换器,或者更详细的绑定(如果(您不想使用转换器))。它仍然是实验性的,但我认为它显示了希望。

回答by ZoomAll

You can use the solution:
Lightweight dependency injection container for JavaScript/TypeScript

您可以使用解决方案:
JavaScript/TypeScript 的轻量级依赖注入容器

import {autoInjectable, container} from "tsyringe";

class MyService {
  move(){
    console.log('myService move 123', );
  }
}

class MyServiceMock {
  move(){
    console.log('mock myService move 777', );
  }
}

@autoInjectable()
export class ClassA {
  constructor(public service?: MyService) {
  }
  move(){
    this.service?.move();
  }
}

container.register(MyService, {
  useClass: MyServiceMock
});

new ClassA().move();

output:

输出:

mock myService move 777

模拟 myService 移动 777

回答by Saleh Shehata

You can give this a shot: https://www.npmjs.com/package/easy-injectionjs. It is a generic use dependency injection package.

您可以试一试:https: //www.npmjs.com/package/easy-injectionjs。它是一个通用的使用依赖注入包。

@EasySingleton creates a single instance of the dependency through the entire application. It is ideal for a service of some sort.

@EasySingleton 通过整个应用程序创建依赖项的单个实例。它是某种服务的理想选择。

@EasyPrototype creates as many instances of the dependency as needed. It is ideal for changeable dependencies.

@EasyPrototype 根据需要创建尽可能多的依赖项实例。它是可变依赖项的理想选择。

@EasyFactory is primarily used for inheritance:

@EasyFactory 主要用于继承:

You can do anything using this package: Simple usage (Coming from the readme):

你可以使用这个包做任何事情:简单用法(来自自述文件):

import { Easy, EasyFactory, EasyPrototype, EasySingleton } from 'easy-injectionjs';

@EasyFactory()
abstract class Person {
  abstract getName();
  abstract setName(v: string);
}

// @EasyObservable()
@EasySingleton()
class Somebody extends Person{
  // @Easy()
  constructor (private name: string) {
    super()
    this.name = 'Sal';
  }

  public getName() {
    return this.name;
  }
  public setName(v: string) {
    this.name = v;
  }
}

@EasyPrototype()
class Nobody extends Person{
  @Easy()
  somebody: Person;
  constructor () {
    super()
  }

  public getName() {
    return this.somebody.getName();
  }

  public setName(v: string) {
    this.somebody.setName(v);
  }
}

@EasyPrototype()
class Data {
  @Easy()
  somebody: Person;
  name: string;

  change(v: string) {
    this.somebody.setName(v);
  }

  getName(): string {
    return this.somebody.getName();
  }
}

let n = new Nobody();
console.log(n.getName()) // Prints Sal
n.setName('awesome');
console.log(n.getName())  // Prints awesome
let d = new Data()
console.log(d.getName())  // Prints awesome
d.change('Gelba')
console.log(n.getName())  // Prints Gelba
d.change('kaa')
console.log(n.getName())  // Prints Kaa

Even if you want to inject node modules you can do this:

即使你想注入节点模块,你也可以这样做:

import * as IExpress from 'express';
import { Easy, EasySingleton } from 'easy-injectionjs';

@EasySingleton()
class Express extends IExpress {} 

@EasySingleton()
export class App {
  @Easy()
  private _express: Express;
}

let app = new App();
console.log(app)

Of course, the usage of the express server isn't for console logging. It is just for testing :D.

当然,快速服务器的用途不是用于控制台日志记录。它仅用于测试:D。

Hope that helps :D

希望有帮助:D

回答by Alex Dresko

TypeScript works well with AMD loaders like requirejs. If confgured properly, TypeScript will output fully AMD compliant javascript.

TypeScript 可以很好地与 AMD 加载器(如 requirejs)配合使用。如果配置正确,TypeScript 将输出完全符合 AMD 的 javascript。

In a testing situation, you could configure requirejs to inject testable modules.

在测试情况下,您可以配置 requirejs 来注入可测试的模块。

回答by user1995470

I work on AutoFixtureTS that is inspired by AutoFixture. AutoFixtureTS makes it easier for TypeScript developers to do Test-Driven Development by automating non-relevant Test Fixture Setup, allowing the Test Developer to focus on the essentials of each test case.

我从事受 AutoFixture 启发的 AutoFixtureTS。AutoFixtureTS 通过自动化不相关的测试夹具设置使 TypeScript 开发人员更容易进行测试驱动开发,允许测试开发人员专注于每个测试用例的要点。

http://ronniehegelund.github.io/AutoFixtureTS/

http://ronniehegelund.github.io/AutoFixtureTS/

Its still just prototype code, but check it out :-)

它仍然只是原型代码,但请检查一下:-)

/ronnie

/罗尼