UNPKG

mana-syringe

Version:

IoC library for mana, easily to use.

238 lines (180 loc) 7.16 kB
# mana-syringe IoC library for mana, easily to use. [![NPM version](https://img.shields.io/npm/v/mana-syringe.svg?style=flat)](https://npmjs.org/package/mana-syringe) [![NPM downloads](https://img.shields.io/npm/dm/mana-syringe.svg?style=flat)](https://npmjs.org/package/mana-syringe) 提供易于使用的依赖注入容器,参考 TSyringe 项目,参考并基于 inversify。 ## 安装 ```bash npm i mana-syringe --save ``` ## 概念与使用 ### 注入标识 token 注入绑定对象所使用的的标识,可以带有一定的类型约束 ```typescript Token<T> = string | symbol | Newable<T> | Abstract<T> | Syringe.DefinedToken<T>; ``` 除 `Syringe.DefinedToken<T>` 默认支持多绑定外,注入标识只支持单一绑定关系。可以使用如下 API 生成 DefinedToken ```typescript Syringe.defineToken('sample-token'); ``` ### 容器 Container 包含一组绑定标识与注入对象关系描述的上下文成为容器,当我们通过容器获取实例时,容器会根据注入对象及其与标识的关系自动构建所需的其他实例。 用户可以手动创建容器,使用全局默认的容器,或者创建子容器 ```typescript import { GlobalContainer, Container } from './container'; const global = GlobalContainer; const container = new Container(); const child = container.createChild(); ``` 我们使用 `token` 从容器里获取对象 ```typescript const ninja = child.get(Ninja); ``` 当我们从子容器中获取对象时,会先从子容器查找绑定关系和缓存信息,如果不存在,则继续向父容器查找。 ### 注册 register 容器上暴露了 register 方法,这个 API 是整个体系的核心。 register 方法有两种签名 ```typescript register<T = any>(options: Syringe.InjectOption<T>): void; register<T = any>(token: Syringe.Token<T>, options?: Syringe.InjectOption<T>): void; ``` 可以调用容器实例上的 register 方法,也可以直接调用全局的 register 方法,其相对于调用 GlobalContainer 的方法。 从签名可以看出,注册绑定需要一组配置,在不同场景下配置会有所不同,可能出现的配置项如下 ```typescript interface { token?: MaybeArray<UnionToken<T>>; contrib?: MaybeArray<Token<T>>; lifecycle?: Lifecycle; useClass?: MaybeArray<Class<T>>; useDynamic?: MaybeArray<Dynamic<T>>; useFactory?: MaybeArray<Factory<T>>; useValue?: T; } ``` - token 可以为数组,本次绑定关系需要声明的标识,不同标识分别注册 - contrib 可以为数组,可用于注册扩展点,也可用于注册 token 别名 - useClass 可以为数组,给出一个或多个类 - useToken 可以为数组,根据 token 从容器内动态获取对象 - useFactory 可以为数组,基于带有容器信息的上下文,给出动态获得实例的方法 - useDynamic 可以为数组,基于带有容器信息的上下文给出实例 - useValue 可以为数组,常量直接给出值 #### 生命期 lifecycle 容器会根据注入对象的生命期描述托管这些对象,决定是否使用缓存等。 ```typescript export enum Lifecycle { singleton = 'singleton', transient = 'transient', } ``` #### 注册类和别名 ```typescript @singleton({ contrib: Alias }) class Shuriken implements Weapon { public hit() { console.log('Shuriken hit'); } } GlobalContainer.register(Shuriken); GlobalContainer.register(Shuriken, { useClass: Shuriken, lifecycle: Syringe.Lifecycle.singleton, }); ``` 通过 token 注册后,每个 token 的注册关系是独立的,通过他们获取对象可以是不同的值,通过 contrib 注册的是别名关系,他们应该获取到同一个对象。不管是 token 还是 contrib,根据对多绑定的支持情况做处理。 ```typescript const Weapon = Symbol('Weapon'); const WeaponArray = Syringe.defineToken('Weapon'); @singleton({ contrib: Weapon }) class Shuriken implements Weapon { public hit() { console.log('Shuriken hit'); } } GlobalContainer.register({ token: Weapon, useValue: undefined }); GlobalContainer.register({ token: WeaponArray, useValue: undefined }); GlobalContainer.register(Shuriken); GlobalContainer.get(Weapon); // Shuriken GlobalContainer.getAll(WeaponArray); // [undefined, Shuriken] ``` #### 注册值 ```typescript const ConstantValue = Symbol('ConstantValue'); GlobalContainer.register({ token: ConstantValue, useValue: {} }); ``` #### 注册动态值 ```typescript const NinjaAlias = Symbol('NinjaAlias'); GlobalContainer.register({ token: NinjaAlias, useDynamic: ctx => ctx.container.get(Ninja), }); ``` ### 装饰器 我们提供了一组对类与属性的装饰器函数,用来快速完成基于依赖注入的类型描述,并完成基本的绑定关系描述。 - injectable: 通用装饰器,接受所有绑定描述参数 - singleton: 单例装饰器,接受除生命期外的描述参数 - transient: 多例装饰器,接受除生命期外的描述参数 - inject: 注入,接受注入标识作为参数,并接受类型描述 ```typescript @singleton() class Shuriken implements Weapon { public hit() { console.log('Shuriken hit'); } } @transient() class Ninja { @inject(Weapon) public weapon: Weapon; public hit() { this.weapon.hit(); } } ``` ### 扩展点 Contribution 我们通常将依赖注入的多绑定模式以扩展点的形式使用,为了方便在项目中使用这种模式,我们内置了对扩展点的定义和支持。 #### 扩展点的定义与注册 ```typescript const Weapon = Syringe.defineToken('Weapon'); Contribution.register(GlobalContainer.register, Weapon); ``` #### 扩展服务 Contribution.Provider 内置了扩展点的管理服务,用户一般直接使用即可,注册扩展点以后,通过如下方式获取扩展服务 ```typescript @contrib(Weapon) public weaponProvider: Contribution.Provider<Weapon>; ``` 等价于如下写法 ```typescript @inject(Contribution.Provider) @named(Weapon) public weaponProvider: Contribution.Provider<Weapon>; ``` #### 扩展点示例 ```typescript const Weapon = Syringe.defineToken('Weapon'); Contribution.register(GlobalContainer.register, Weapon); @singleton({ contrib: Weapon }) class Shuriken implements Weapon { public hit() { console.log('Shuriken hit'); } } @transient() class Ninja { @contrib(Weapon) public weaponProvider: Contribution.Provider<Weapon>; hit() { const weapons = this.weaponProvider.getContributions(); weapons.forEach(w => w.hit()); } } const module = Module(register => { Contribution.register(register, Weapon); register(Shuriken); register(Ninja); }); GlobalContainer.register(Shuriken); GlobalContainer.register(Ninja); GlobalContainer.get(Ninja).hit(); // Shuriken hit ``` ### 模块 可以通过用一组注册动作创建一个模块,方便在不同容器上下文间内加载, 模块的构建支持注册函数和链式调用两种方式,前面扩展点示例里的模块也可以写成如下形式: ```typescript const module = Module().contribution(Weapon).register(Shuriken, Ninja); GlobalContainer.load(module); ``` - 相同 module 默认不重复加载。