【TypeScript】 装饰器的应用, Metadata 元数据的应用

读一篇关于依赖注入的文章时,发现看不懂在写什么了。。于是就有了这篇文章

这就是看不懂的那篇 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就行。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值