读一篇关于依赖注入的文章时,发现看不懂在写什么了。。于是就有了这篇文章
这就是看不懂的那篇 https://juejin.cn/post/6898882861277904910
关于Metadata
按我的理解,其实就是一些不能被直接访问的属性,里面可以保存很多类型的东西。。。
相关api https://github.com/rbuckton/reflect-metadata#api
关于装饰器的使用
Ts装饰器官方文档:https://www.tslang.cn/docs/handbook/decorators.html
// provider.ts
import 'reflect-metadata' // 开启metadata支持
export const CLASS_KEY = 'ioc:tagged_class';
export function Provider(identifier: string, args?: Array<any>) {
return function (target: any) { // 类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
Reflect.defineMetadata(CLASS_KEY, {
id: identifier,
args: args || []
}, target); // 结合下面,class B 的构造函数就是这里的 target
return target;
};
}
// b.ts
import { Provider } from 'provider';
@Provider('b', [10]) // 使用的时候手动传入identifier,args,然后返回一个函数,ts自动传入类的构造函数
export class B {
constructor(p: number) {
this.p = p;
}
}
// load.ts 扫描代码文件,根据导入的文件中是否有元数据 CLASS_KEY 判断是否需要注册。
import * as fs from 'fs';
import { CLASS_KEY } from './provider';
export function load(container) { // container 为全局的 IoC 容器
const list = fs.readdirSync('./');
for (const file of list) {
if (/\.ts$/.test(file)) { // 扫描 ts 文件
const exports = require(`./${file}`);
for (const m in exports) {
const module = exports[m];
if (typeof module === 'function') {
const metadata = Reflect.getMetadata(CLASS_KEY, module); // CLASS_KEY是统一定义的标识符,用来在统一注册容器中标识依赖
// 注册实例到统一管理容器中
if (metadata) {
container.bind(metadata.id, module, metadata.args)
}
}
}
}
}
}
// inject.ts
// inject作为属性装饰器调用,属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:
// 1.对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
// 2.成员的名字。
import 'reflect-metadata';
export const PROPS_KEY = 'ioc:inject_props';
export function Inject() {
// 在最底下的例子中,传入(Class A.prototype,属性'b')
return function (target: any, targetKey: string) {
const annotationTarget = target.constructor; // 这个和原型链有关,class.prototype.constructor即可访问到构造函数
let props = {};
// 检查之前是否有inject过其它的,若有注入过,则先拿出之前注册的数据,再添加新的。
if (Reflect.hasOwnMetadata(PROPS_KEY, annotationTarget)) {
props = Reflect.getMetadata(PROPS_KEY, annotationTarget);
}
props[targetKey] = {
value: targetKey // 保存target key,用于获取实例时使用
};
Reflect.defineMetadata(PROPS_KEY, props, annotationTarget); // 把注入的数据保存到annotationTarget中
};
}
// container.ts // 托管构造函数的地方,获取实例的地方
import { PROPS_KEY } from './inject';
export class Container {
bindMap = new Map();
bind(identifier: string, clazz: any, constructorArgs?: Array<any>) {
this.bindMap.set(identifier, {
clazz,
constructorArgs: constructorArgs || []
});
}
// 获取实例用的方法
get<T>(identifier: string): T {
const target = this.bindMap.get(identifier);
const { clazz, constructorArgs } = target;
const props = Reflect.getMetadata(PROPS_KEY, clazz); // 获取需要注入的依赖列表
const inst = Reflect.construct(clazz, constructorArgs); // 创建所get的对象的实例
// 根据列表往实例中注入依赖
for (let prop in props) {
const identifier = props[prop].value; // inject 时保存下来的 targetKey
// 递归获取注入的对象
inst[ prop ] = this.get(identifier);
}
return inst;
}
}
// b.ts
@Proivder('b', [10])
class B {
constructor(p: number) {
this.p = p;
}
}
// a.ts
@Proivder('a')
class A {
@Inject()
private b:B;
}
// main.ts
const container = new Container();
load(container);
console.log(container.get('a')); // => A { b: B { p: 10 } } // 此后获取实例无需new,直接从容器get就行。