项目地址
灵感来自 angular 和我自己写的框架 indiv https://github.com/DimaLiLongJi/InDiv
感谢大佬们 star 使用
- https://github.com/DimaLiLongJi/common-injector
- https://www.npmjs.com/package/common-injector
关于依赖注入
IoC(Inversion of Control) 即控制反转, DI(Dependency Injection) 即依赖注入.
假设我们有一个类 Humen, 要实例一个 Humen, 我们需要实例一个类 Clothes. 而实例化衣服 Clothes, 我们又需要实例化布 Cloth, 实例化纽扣等等.
当需求达到一定复杂的程度时, 我们不能为了一个人穿衣服去从布从纽扣开始从头实现, 最好能把所有的需求放到一个工厂或者是仓库, 我们需要什么直接从工厂的仓库里面直接拿.
这个时候就需要依赖注入了, 我们实现一个 IoC 容器 (仓库), 然后需要衣服就从仓库里面直接拿实例好的衣服给人作为属性穿上去.
IoC 是一种很好的解耦合思想, 在开发中, IoC 意味着你设计好的对象交给容器控制, 而不是使用传统的方式, 在对象内部直接控制. 在软件开发中有很好的作用, 不仅被应用在 JavaEE 里, 在其它语言里同样适用.
其实总结起来大概就是你需要我, 工厂就把我打扮好派我去找你.
实现一个 IoC 容器
因为 key 类型不一定是字符型, 而且数组遍历比较浪费性能, 因此不选择 Object 和 Array 而选择 Map 作为容器的数据结构.
因为目标是实现一个可以懒汉模式的 IoC 容器, 所以可以在类 Injector 里写了两个私有类, 一个存 token 和依赖, 一个存 token 和实例化的依赖实例.
在需要某个依赖实例的时候, 先去实例的 Map 中根据 token 查找出对应的 token 的实例, 如果没有就从存放依赖类的 Map 中拿出来实例化之后再放入存放实例的 Map.
- export class Injector {
- private readonly providerMap: Map<any, any> = new Map();
- private readonly instanceMap: Map<any, any> = new Map();
- public setProvider(key: any, value: any): void {
- if (!this.providerMap.has(key)) this.providerMap.set(key, value);
- }
- public getProvider(key: any): any {
- return this.providerMap.get(key);
- }
- public setInstance(key: any, value: any): void {
- if (!this.instanceMap.has(key)) this.instanceMap.set(key, value);
- }
- public getInstance(key: any): any {
- if (this.instanceMap.has(key)) return this.instanceMap.get(key);
- return null;
- }
- public setValue(key: any, value: any): void {
- if (!this.instanceMap.has(key)) this.instanceMap.set(key, value);
- }
- }
现在 new 一下, 这个容器就创建出来了.
export const rootInjector = new Injector();
实现服务
偷偷借走了 ng 的 Injectable, 通过类装饰器把类存入容器.
- export function Injectable(): (_constructor: any) => any {
- return function (_constructor: any): any {
- rootInjector.setProvider(_constructor, _constructor);
- return _constructor;
- };
- }
把类作为 token, 把该类存入 provider 容器, 提供给需要依赖的类.
实现基于注解的属性注入
之所以不实现构造注入, setter 注入是因为像 ng 和 react 这类框架会对直接对构造函数进行注入或是限制构造函数的参数, 为了尽量跨框架跨前后端使用, 所以还是用装饰器对属性注入尽量减少侵入性.
属性装饰器和反射能帮我们实现这一功能.
- export function Inject(): (_constructor: any, propertyName: string) => any {
- return function (_constructor: any, propertyName: string): any {
- const propertyType: any = Reflect.getMetadata('design:type', _constructor, propertyName);
- const injector: Injector = rootInjector;
- let providerInsntance = injector.getInstance(propertyType);
- if (!providerInsntance) {
- injector.getProvider(propertyType);
- providerInsntance = new providerClass();
- injector.setInstance(key, providerInsntance);
- }
- _constructor[propertyName] = providerInsntance;
- return (_constructor as any)[propertyName];
- };
- }
使用 Reflect 的元数据 Reflect.getMetadata('design:type') 获取属性的类型, 并作为 token 去 injector.getInstance 查询对应的实例, 如果有则直接将属性映射为查找到的实例. 这样就保证我们每次使用装饰器的属性都会获得单例.
如果没有查询到则去另外一个 Map 中拿出依赖并实例化存入实例的 Map. 因为 JS 单线程懒汉模式不存在线程安全这么一说, 所以没有选择初始化时就把全部的依赖实例化.
- demo
- import { Inject, Injectable } from '../injector';
- @Injectable()
- class Cloth {
- public name: string = '麻布';
- }
- @Injectable()
- class Clothes {
- @Inject() public cloth: Cloth;
- }
- class Humen {
- @Inject() public clothes: Clothes;
- }
- const pepe = new Humen();
- console.log(pepe);
- // {
- // clothes: {
- // cloth: {
- // name: '麻布'
- // }
- // }
- //}
最后我们可以直接 new pepe, 不需要关心 pepe 穿的衣服 Clothes 和衣服需要的布料 Cloth.
最后推荐结合 rxjs 做响应式编程. ng 大法好!
来源: https://juejin.im/post/5c4e86fe6fb9a049dc02a1b1