详解Metadata 和 Reflector

34 阅读2分钟

装饰器声明后,启动 Nest 应用,对象会创建好,依赖也给注入了,为啥 ?

先看 Reflect 的 metadata

Reflect.get 是获取对象属性值

image.png

Reflect.set 是设置对象属性值 image.png

Reflect.has 是判断对象属性是否存在

image.png

Reflect.apply 是调用某个方法,传入对象和参数

image.png

Reflect.construct 是用构造器创建对象实例,传入构造器参数

image.png

Nest 用到的 api

Reflect.defineMetadata(metadataKey, metadataValue, target);

Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey);


let result = Reflect.getMetadata(metadataKey, target);

let result = Reflect.getMetadata(metadataKey, target, propertyKey);

Reflect.defineMetadata 和 Reflect.getMetadata 分别用于设置和获取某个类的元数据,如果最后传入了属性名,还可以单独为某个属性设置元数据

那元数据存在哪?

存在类或者对象上,如果给类或者类的静态属性添加元数据,那就保存在类上

如果给实例属性添加元数据,那就保存在对象上,用类似 [[metadata]] 的 key 来存

支持装饰器的方式使用:

@Reflect.metadata(metadataKey, metadataValue)
class C {

  @Reflect.metadata(metadataKey, metadataValue)
  method() {
  }
}

基础封装

function Meta(metadataKey: string, metadataValue: any) {
  return Reflect.metadata(metadataKey, metadataValue)
}

实际例子

function Validate() {
  return function (target, propertyKey, descriptor) {
    const types = Reflect.getMetadata(
      'design:paramtypes',
      target,
      propertyKey
    )

    const original = descriptor.value

    descriptor.value = function (...args) {
      args.forEach((arg, index) => {
        const Type = types[index]
        if (!(arg instanceof Type) && typeof arg !== Type.name.toLowerCase()) {
          throw new Error(
            `参数 ${index} 类型错误,期望 ${Type.name}`
          )
        }
      })
      return original.apply(this, args)
    }
  }
}

使用

class MathService {

  @Validate()
  add(a: number, b: number) {
    return a + b
  }
}

new MathService().add(1, 2)     // ✅
new MathService().add('1', 2)   // ❌

Nest 的实现原理就是通过装饰器给 class 或者对象添加元数据,然后初始化的时候取出这些元数据,进行依赖的分析,然后创建对应的实例对象就可以了。

所以说,nest 实现的核心就是 Reflect metadata 的 api

安装依赖 & 打开配置

npm i reflect-metadata

tsconfig.json

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

入口文件最顶部:

import 'reflect-metadata'

自己写一个装饰器

function MyController(path: string): ClassDecorator {
  return (target) => {
    Reflect.defineMetadata('path', path, target)
  }
}

这一行是核心:

Reflect.defineMetadata(key, value, target)

👆 把 path 这个信息,挂到 class 上

使用这个装饰器

@MyController('/user')
class UserController {}

运行时把元数据读出来

const path = Reflect.getMetadata('path', UserController)
console.log(path) // /user

🔑 到这里你已经实现了 Nest 的第一步

Nest 的 Controller / Injectable 本质

Controller 装饰器 ≈

@Controller('/user')
class UserController {}

等价于:

Reflect.defineMetadata('path', '/user', UserController)

Injectable 装饰器 ≈

@Injectable()
class UserService {}

等价于:

Reflect.defineMetadata('injectable', true, UserService)

依赖注入是怎么“自动知道”的?

关键:构造函数参数的类型元数据

@Injectable()
class UserService {}
@Injectable()
class UserController {
  constructor(private userService: UserService) {}
}

TypeScript 在背后偷偷干了什么?

开启 emitDecoratorMetadata 后:

Reflect.getMetadata('design:paramtypes', UserController)

得到:

[ UserService ]

💥 这一步是 Nest DI 的灵魂


import 'reflect-metadata'

@Injectable()
class UserService {}

@Injectable()
class UserController {
  constructor(private userService: UserService) {}
}

const deps = Reflect.getMetadata(
  'design:paramtypes',
  UserController
)

console.log(deps)
// [ [class UserService] ]

Nest 初始化时到底在干嘛?

可以理解成这几步👇

function bootstrap() {
  // 1. 扫描所有 class
  // 2. 找 @Controller / @Injectable
  // 3. 读取元数据
  // 4. 分析依赖
  // 5. new 实例
}

极简 DI 容器

const container = new Map()

function createInstance(target) {
  const deps =
    Reflect.getMetadata('design:paramtypes', target) || []

  const params = deps.map(dep => {
    if (!container.has(dep)) {
      container.set(dep, createInstance(dep))
    }
    return container.get(dep)
  })

  return new target(...params)
}

使用:

const controller = createInstance(UserController)

👉 这就是 Nest DI 的雏形