]InversifyJS 2.0:一个强大的轻量级IoC容器,供JavaScript应用程序使用

・19 分钟阅读

介绍

InversifyJS是一个用于typeScript应用程序的开源(IoC)容器。

InversifyJS是用于TypeScript和JavaScript应用程序的功能强大的轻量级( 4KB )pico inversion控制( IoC )容器,pico IoC容器使用类构造函数来标识和注入它依赖项,InversifyJS有一个友好的API,鼓励使用最好的OOP和IoC实践。

动机

InversifyJS被设计成允许JavaScript开发人员编写符合可靠原则的代码。

哲学

InversifyJS已经完成开发了3个主要目标:

允许JavaScript开发人员编写符合可靠原则的代码。

简化并鼓励遵循最佳OOP和IoC实践。

尽可能减少运行时开销。

nate Kohari- Ninject的作者

background

我大约一年前发布了InversifyJS 1.0,

InversifyJS 2.0是轻量级库,主要目标之一是尽可能少地增加运行时开销,最新的版本只有4KB (gzip ),并且没有依赖项。

基本知识(TypeScript )

InversifyJS的基本用法和API和typeScript:

步骤1:声明你的接口

我们的目标是编写符合依赖倒置原则(dependency inversion principle),首先声明一些接口(抽象)。

<code>interface INinja {
 fight(): string;
 sneak(): string;
}interface IKatana {
 hit(): string;
}interface IShuriken {
 throw();
}</code>

步骤2:使用@inject修饰符实现接口和声明依赖项

类是刚刚声明的接口的实现。

<code>import { inject } from"inversify";class Katana implements IKatana {
 public hit() {
 return"cut!";
 }
}class Shuriken implements IShuriken {
 publicthrow() {
 return"hit!";
 }
}
@inject("IKatana", "IShuriken")class Ninja implements INinja {
 private _katana: IKatana;
 private _shuriken: IShuriken;
 public constructor(katana: IKatana, shuriken: IShuriken) {
 this._katana = katana;
 this._shuriken = shuriken;
 }
 public fight() { returnthis._katana.hit(); };
 public sneak() { returnthis._shuriken.throw(); };
}</code>

步骤3:创建和配置内核

建议在名为inversify.config.ts的文件中执行此操作,这是唯一一个有耦合的地方,在应用程序的其余部分,你的类应该没有对其他类的引用。

<code>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;</code>

步骤4:解决依赖关系

可以使用get<T>方法从Kernel类解析依赖项,

<code>import kernel = from"./inversify.config";var ninja = kernel.get<INinja>("INinja");
expect(ninja.fight()).eql("cut!"); // trueexpect(ninja.sneak()).eql("hit!"); // true</code>

可以看到IKatanaIShuriken已成功解析并注入到Ninja

基本知识(JavaScript )

建议使用typeScript来获得最佳开发体验,但是,如果您愿意的话,可以使用纯JavaScript,

<code>var inversify = require("inversify");
require("reflect-metadata");var TYPES = {
 Ninja: "Ninja",
 Katana: "Katana",
 Shuriken: "Shuriken"};class Katana {
 hit() {
 return"cut!";
 }
}class Shuriken {
 throw() {
 return"hit!";
 }
}class Ninja {
 constructor(katana, shuriken) {
 this._katana = katana;
 this._shuriken = shuriken;
 }
 fight() { returnthis._katana.hit(); };
 sneak() { returnthis._shuriken.throw(); };
}// Declare injectionsinversify.inject(TYPES.Katana, TYPES.Shuriken)(Ninja);// Declare bindingsvar kernel = new inversify.Kernel();
kernel.bind(TYPES.Ninja).to(Ninja);
kernel.bind(TYPES.Katana).to(Katana);
kernel.bind(TYPES.Shuriken).to(Shuriken);// Resolve dependenciesvar ninja = kernel.get(TYPES.Ninja);return ninja;</code>

功能(v2.0.0 alpha.3 )

让看看InversifyJS的特性!

声明内核模块

内核模块可以帮助你管理大型应用程序中绑定的复杂性。

<code>let someModule: IKernelModule = (kernel: IKernel) => {
 kernel.bind<INinja>("INinja").to(Ninja);
 kernel.bind<IKatana>("IKatana").to(Katana);
 kernel.bind<IShuriken>("IShuriken").to(Shuriken);
};let kernel = new Kernel({ modules: [ someModule ] });</code>

控制依赖项的范围

InversifyJS默认使用临时作用域,但是也可以使用单例作用域:

<code>kernel.bind<IShuriken>("IShuriken").to(Shuriken).inTransientScope(); // Defaultkernel.bind<IShuriken>("IShuriken").to(Shuriken).inSingletonScope();</code>

插入值

将抽象绑定到常数值。

<code>kernel.bind<IKatana>("IKatana").toValue(new Katana());</code>

注入类构造函数

将抽象绑定到类构造函数。

<code>@inject("IKatana", "IShuriken")class Ninja implements INinja {
 private _katana: IKatana;
 private _shuriken: IShuriken;
 public constructor(Katana: INewable<IKatana>, shuriken: IShuriken) {
 this._katana = new Katana();
 this._shuriken = shuriken;
 }
 public fight() { returnthis._katana.hit(); };
 public sneak() { returnthis._shuriken.throw(); };
}</code>
<code>kernel.bind<INewable<IKatana>>("INewable<IKatana>").toConstructor<IKatana>(Katana);</code>

注入工厂

将抽象绑定到用户定义的工厂。

<code>@inject("IKatana", "IShuriken")class Ninja implements INinja {
 private _katana: IKatana;
 private _shuriken: IShuriken;
 public constructor(katanaFactory: IFactory<IKatana>, shuriken: IShuriken) {
 this._katana = katanaFactory();
 this._shuriken = shuriken;
 }
 public fight() { returnthis._katana.hit(); };
 public sneak() { returnthis._shuriken.throw(); };
}</code>
<code>kernel.bind<IFactory<IKatana>>("IFactory<IKatana>").toFactory<IKatana>((context) => {
 return () => {
 return context.kernel.get<IKatana>("IKatana");
 };
});</code>

自动工厂

将抽象绑定到自动生成的工厂。

<code>@inject("IKatana", "IShuriken")class Ninja implements INinja {
 private _katana: IKatana;
 private _shuriken: IShuriken;
 public constructor(katanaFactory: IFactory<IKatana>, shuriken: IShuriken) {
 this._katana = katanaFactory();
 this._shuriken = shuriken;
 }
 public fight() { returnthis._katana.hit(); };
 public sneak() { returnthis._shuriken.throw(); };
}</code>
<code>kernel.bind<IFactory<IKatana>>("IFactory<IKatana>").toAutoFactory<IKatana>();</code>

注入Provider (异步工厂)

将抽象绑定到Provider 程序,Provider是一个异步工厂,这在处理异步io操作时很有用。

<code>@inject("IKatana", "IShuriken")class Ninja implements INinja {
 public katana: IKatana;
 public shuriken: IShuriken;
 public katanaProvider: IProvider<IKatana>;
 public constructor(katanaProvider: IProvider<IKatana>, shuriken: IShuriken) {
 this.katanaProvider = katanaProvider;
 this.katana= null;
 this.shuriken = shuriken;
 }
 public fight() { returnthis._katana.hit(); };
 public sneak() { returnthis._shuriken.throw(); };
}var ninja = kernel.get<INinja>("INinja");
ninja.katanaProvider()
. then((katana) => { ninja.katana = katana; })
. catch((e) => { console.log(e); });</code>
<code>kernel.bind<IProvider<IKatana>>("IProvider<IKatana>").toProvider<IKatana>((context) => {
 return () => {
 returnnew Promise<IKatana>((resolve) => {
 let katana = context.kernel.get<IKatana>("IKatana");
 resolve(katana);
 });
 };
});</code>

注入代理

在注入依赖项之前,可以创建依赖项的代理,这有助于使的依赖与横切关注点的实现无关,比如,缓存或日志记录。

<code>interface IKatana {
 use: () =>void;
}class Katana implements IKatana {
 public use() {
 console.log("Used Katana!");
 }
}interface INinja {
 katana: IKatana;
}
@inject("IKatana")class Ninja implements INinja {
 public katana: IKatana;
 public constructor(katana: IKatana) {
 this.katana = katana;
 }
}</code>
<code>kernel.bind<INinja>("INinja").to(Ninja);
kernel.bind<IKatana>("IKatana").to(Katana).proxy((katana) => {
 let handler = {
 apply: function(target, thisArgument, argumentsList) {
 console.log(`Starting: ${new Date().getTime()}`);
 let result = target.apply(thisArgument, argumentsList);
 console.log(`Finished: ${new Date().getTime()}`);
 return result;
 }
 };
 katana.use = new Proxy(katana.use, handler);
 return katana;
});</code>
<code>let ninja = kernelget<INinja>();
ninja.katana.use();> Starting: 1457895135761> Used Katana!> Finished: 1457895135762</code>

多注入

当两个或多个concretions被绑定到一个抽象时,可以使用多注入,注意IWeapon的数组是如何通过它构造函数注入到Ninja类中的:

<code>interface IWeapon {
 name: string;
}class Katana implements IWeapon {
 public name = "Katana";
}class Shuriken implements IWeapon {
 public name = "Shuriken";
}interface INinja {
 katana: IWeapon;
 shuriken: IWeapon;
}
@inject("IWeapon[]")class Ninja implements INinja {
 public katana: IWeapon;
 public shuriken: IWeapon;
 public constructor(weapons: IWeapon[]) {
 this.katana = weapons[0];
 this.shuriken = weapons[1];
 }
}</code>

正在将KatanaShuriken绑定到IWeapon

<code>kernel.bind<INinja>("INinja").to(Ninja);
kernel.bind<IWeapon>("IWeapon").to(Katana);
kernel.bind<IWeapon>("IWeapon").to(Shuriken);</code>

标记绑定

当两个或多个concretions被绑定到一个抽象时,可以使用标记绑定来修复AMBIGUOUS_MATCH错误,注意Ninja类的构造函数参数是如何使用@tagged decorator注释的:

<code>interface IWeapon {}class Katana implements IWeapon { }class Shuriken implements IWeapon {}interface INinja {
 katana: IWeapon;
 shuriken: IWeapon;
}
@inject("IWeapon", "IWeapon")class Ninja implements INinja {
 public katana: IWeapon;
 public shuriken: IWeapon;
 public constructor(
 @tagged("canThrow", false) katana: IWeapon,
 @tagged("canThrow", true) shuriken: IWeapon
 ) {
 this.katana = katana;
 this.shuriken = shuriken;
 }
}</code>

KatanaShuriken绑定到IWeapon ,但是添加了whenTargetTagged约束以避免错误:

<code>kernel.bind<INinja>(ninjaId).to(Ninja);
kernel.bind<IWeapon>(weaponId).to(Katana).whenTargetTagged("canThrow", false);
kernel.bind<IWeapon>(weaponId).to(Shuriken).whenTargetTagged("canThrow", true);</code>

创建你自己的标记修饰符

创建你自己的decorator非常简单:

<code>let throwable = tagged("canThrow", true);let notThrowable = tagged("canThrow", false);
@inject("IWeapon", "IWeapon")class Ninja implements INinja {
 public katana: IWeapon;
 public shuriken: IWeapon;
 public constructor(
 @notThrowable katana: IWeapon,
 @throwable shuriken: IWeapon
 ) {
 this.katana = katana;
 this.shuriken = shuriken;
 }
}</code>

命名绑定

当两个或多个concretions被绑定到一个抽象时,可以使用命名绑定来修复AMBIGUOUS_MATCH错误,注意Ninja类的构造函数参数是如何使用@named decorator注释的:

<code>interface IWeapon {}class Katana implements IWeapon { }class Shuriken implements IWeapon {}interface INinja {
 katana: IWeapon;
 shuriken: IWeapon;
}
@inject("IWeapon", "IWeapon")class Ninja implements INinja {
 public katana: IWeapon;
 public shuriken: IWeapon;
 public constructor(
 @named("strong")katana: IWeapon,
 @named("weak") shuriken: IWeapon
 ) {
 this.katana = katana;
 this.shuriken = shuriken;
 }
}</code>

KatanaShuriken绑定到IWeapon ,但是添加了whenTargetNamed约束以避免错误:

<code>kernel.bind<INinja>("INinja").to(Ninja);
kernel.bind<IWeapon>("IWeapon").to(Katana).whenTargetNamed("strong");
kernel.bind<IWeapon>("IWeapon").to(Shuriken).whenTargetNamed("weak");</code>

上下文绑定& amp;@paramNames

@paramNames修饰符用于从上下文约束访问构造函数参数的名称,即使代码被压缩了,constructor(katana, shuriken) {... 变成constructor(a, b) {... 压缩后,但是由于@paramNames,仍然可以参考设计$time names KatanaShuriken

<code>interface IWeapon {}class Katana implements IWeapon { }class Shuriken implements IWeapon {}interface INinja {
 katana: IWeapon;
 shuriken: IWeapon;
}
@inject("IWeapon", "IWeapon")
@paramNames("katana","shuriken")class Ninja implements INinja {
 public katana: IWeapon;
 public shuriken: IWeapon;
 public constructor(
 katana: IWeapon,
 shuriken: IWeapon
 ) {
 this.katana = katana;
 this.shuriken = shuriken;
 }
}</code>

KatanaShuriken绑定到IWeapon,但是要避免自定义,

<code>kernel.bind<INinja>(ninjaId).to(Ninja);
kernel.bind<IWeapon>("IWeapon").to(Katana).when((request: IRequest) => {
 return request.target.name.equals("katana");
});
kernel.bind<IWeapon>("IWeapon").to(Shuriken).when((request: IRequest) => {
 return request.target.name.equals("shuriken");
});</code>

目标字段实现IQueryableString接口,以帮助你创建自定义约束:

<code>interface IQueryableString {
 startsWith(searchString: string): boolean;
 endsWith(searchString: string): boolean;
 contains(searchString: string): boolean;
 equals(compareString: string): boolean;
 value(): string;
}</code>

循环依赖

InversifyJS能够标识循环依赖项,并且会引发异常,以帮助你在检测到循环依赖项时标识问题的位置:

<code>Error: Circular dependency found between services: IKatana and INinja</code>

在线演示(ES6 )

你可以在线尝试InversifyJS - tonicdev.com

讨论
学以致用 profile image