InversifyJS

一个强大又轻量的控制反转容器,提供给JavaScript 和 Node.js 应用使用,使用TypeScript编写。

简介

InversifyJS 是一个轻量的 (4KB) 控制反转容器 (IoC),可用于编写 TypeScript 和 JavaScript 应用。
它使用类构造函数去定义和注入它的依赖。InversifyJS API 很友好易懂, 鼓励对 OOP 和 IoC 最佳实践的应用.

为什么要有 InversifyJS?

JavaScript 现在支持面向对象编程,基于类的继承。 这些特性不错但事实上它们也是
危险的
我们需要一个优秀的面向对象设计(比如 SOLID),Composite Reuse等)来保护我们避免这些威胁。然而,面向对象的设计是复杂的,所以我们创建了 InversifyJS。

InversifyJS 是一个工具,它能帮助 JavaScript 开发者,写出出色的面向对象设计的代码。

目标

InversifyJS有4个主要目标:

  1. 允许JavaScript开发人员编写遵循 SOLID 原则的代码。

  2. 促进并鼓励遵守最佳的面向对象编程和依赖注入实践。

  3. 尽可能少的运行时开销。

  4. 提供艺术编程体验和生态

业内评价

Nate Kohari - Ninject的作者

“Nice work! I’ve taken a couple shots at creating DI frameworks for JavaScript and TypeScript, but the lack of RTTI really hinders things.The ES7 metadata gets us part of the way there (as you’ve discovered). Keep up the great work!”

Michel Weststrate - MobX的作者

Dependency injection like InversifyJS works nicely

使用 InversifyJS 的公司

安装

您可以使用npm获得最新的版本和类型定义:

1
$ npm install inversify reflect-metadata --save

Inversify npm 包已经包含了 InversifyJS 的类型定义

:警示: 重要! InversifyJS 需要 TypeScript 的版本 >= 2.0 还有 experimentalDecorators, emitDecoratorMetadata, types and libtsconfig.json 中 compilerOptions 的配置如下:

1
2
3
4
5
6
7
8
9
10
11
{
"compilerOptions": {
"target": "es5",
"lib": ["es6"],
"types": ["reflect-metadata"],
"module": "commonjs",
"moduleResolution": "node",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}

inversifyjs需要现代JavaScript引擎,支持以下特性

如果您的运行环境不支持这些特性,您可能需要导入 shimpolyfill

:警示: reflect-metadata polyfill 应该在您整个应用中只导入一次 因为 Reflect 对象需要成为一个全局的单例。 更多细节可以在这里找到。

查看维基中的开发环境 polyfills
, 还可以从基本示例中去学习.

基础部分

让我们一起看下 inversifyjs 的基本用法和 API:

步骤 1: 声明接口和类型

我们的目标是编写遵循依赖倒置原则的代码。

这意味着我们应该 ”依赖于抽象而不依赖于具体实现“ 。

让我们先声明一些接口(抽象)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// file interfaces.ts

interface Warrior {
fight(): string;
sneak(): string;
}

interface Weapon {
hit(): string;
}

interface ThrowableWeapon {
throw(): string;
}

Inversifyjs 需要在运行时使用类型标记作为标识符。接下来将使用 Symbol 作为标识符,您也可以使用类或字符串。

1
2
3
4
5
6
7
8
9
// file types.ts

const TYPES = {
Warrior: Symbol.for("Warrior"),
Weapon: Symbol.for("Weapon"),
ThrowableWeapon: Symbol.for("ThrowableWeapon")
};

export { TYPES };

警示: 推荐使用 Symbol,但 InversifyJS 也支持使用类和字符串字面值 (请查阅特性部分了解更多)。

步骤 2: 使用 @injectable@inject 装饰器声明依赖

让我们来声明一些类,实现刚刚声明接口。他们都需要使用 @injectable 装饰器去注解。

当一个类依赖于某个接口时,我们也需要使用 @inject 装饰器,来定义在运行时可用的接口标识。在这种情况下,我们将使用 Symbol, 如 Symbol.for("Weapon")Symbol.for("ThrowableWeapon") 作为运行时的标识。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// file entities.ts

import { injectable, inject } from "inversify";
import "reflect-metadata";
import { Weapon, ThrowableWeapon, Warrior } from "./interfaces"
import { TYPES } from "./types";

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

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

@injectable()
class Ninja implements Warrior {

private _katana: Weapon;
private _shuriken: ThrowableWeapon;

public constructor(
@inject(TYPES.Weapon) katana: Weapon,
@inject(TYPES.ThrowableWeapon) shuriken: ThrowableWeapon
) {
this._katana = katana;
this._shuriken = shuriken;
}

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

}

export { Ninja, Katana, Shuriken };

如果您更喜欢使用属性注入而不是构造函数注入,那就可以不用声明类的构造函数了:

1
2
3
4
5
6
7
@injectable()
class Ninja implements Warrior {
@inject(TYPES.Weapon) private _katana: Weapon;
@inject(TYPES.ThrowableWeapon) private _shuriken: ThrowableWeapon;
public fight() { return this._katana.hit(); }
public sneak() { return this._shuriken.throw(); }
}

步骤 3: 创建和配置容器

推荐在命名为 inversify.config.ts 的文件中创建和配置容器。这是唯一有耦合的地方。
在您项目其余部分中的类,不应该包含对其他类的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
// file inversify.config.ts

import { Container } from "inversify";
import { TYPES } from "./types";
import { Warrior, Weapon, ThrowableWeapon } from "./interfaces";
import { Ninja, Katana, Shuriken } from "./entities";

const myContainer = new Container();
myContainer.bind<Warrior>(TYPES.Warrior).to(Ninja);
myContainer.bind<Weapon>(TYPES.Weapon).to(Katana);
myContainer.bind<ThrowableWeapon>(TYPES.ThrowableWeapon).to(Shuriken);

export { myContainer };

步骤 4: 解析依赖

您可以使用方法 get<T>Container 中获得依赖。记得您应该在根结构(尽可能靠近应用程序的入口点的位置)去解析依赖,避免服务器定位反模式

1
2
3
4
5
6
7
8
import { myContainer } from "./inversify.config";
import { TYPES } from "./types";
import { Warrior } from "./interfaces";

const ninja = myContainer.get<Warrior>(TYPES.Warrior);

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

正如我们所看到的 Katana and Shuriken 被成功的解析和注入进 Ninja

InversifyJS 支持 ES5 和 ES6 而且可以在没有 TypeScript 环境下使用。
前往 JavaScript 示例了解更多

InversifyJS 特性 和 API

让我们一起看看 InversifyJS 的特性!

请查阅 wiki 获取更多细节。

生态

为了提供艺术般的开发体验,我们也不断努力:

请查阅 生态 wiki 页 去了解更多。

Support

如果您遇到任何问题,我们乐意帮忙。您可以使用 问题页 报告问题。

如果您想要和开发团队分享您的想法或者加入我们,您可以参加 论坛讨论。您也可以查看 wiki 来了解更多关于 InversifyJS。

Acknowledgements

Thanks a lot to all the contributors, all the developers out there using InversifyJS and all those that help us to spread the word by sharing content about InversifyJS online. Without your feedback and support this project would not be possible.

License

License under the MIT License (MIT)

Copyright © 2015-2017 Remo H. Jansen

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.

IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.