reflect-metadata作用

535 阅读3分钟

reflect-metadata 的用法进行系统性、全覆盖的讲解,内容涵盖以下结构:


🧠 一、基础概念与原理

1.1 什么是 Reflect Metadata?

  • ECMAScript 的 Reflect API 原本提供的是对对象操作的反射能力,如 Reflect.get()
  • reflect-metadata 扩展了 Reflect,允许你为类、属性、参数、方法附加元数据(metadata),并在运行时获取这些信息。

这在 JavaScript 本身是做不到的,TypeScript 提供了 emitDecoratorMetadata 选项,让它成为可能。

1.2 元数据的作用

  • 在框架设计中用于:依赖注入、路由注册、权限控制、序列化、验证等。
  • 特别适合用于**装饰器(Decorator)**中,通过装饰器记录额外信息。

🧪 二、完整 API 说明

方法描述
Reflect.defineMetadata(metadataKey, metadataValue, target, [propertyKey])定义元数据
Reflect.hasMetadata(metadataKey, target, [propertyKey])是否存在元数据
Reflect.getMetadata(metadataKey, target, [propertyKey])获取元数据
Reflect.deleteMetadata(metadataKey, target, [propertyKey])删除元数据
Reflect.getOwnMetadata()getMetadata 类似,但只获取自身定义的,不包含继承的

🛠️ 三、装饰器中常用的 TypeScript 自动注入类型元数据

当启用 emitDecoratorMetadata,可以通过以下 metadata key 获取类型信息:

metadata key用途
design:type属性类型
design:paramtypes方法参数类型(数组)
design:returntype方法返回类型

📘 四、用法全案例详解

✅ 4.1 装饰属性并获取其类型

import 'reflect-metadata';

class Demo {
  @Reflect.metadata('custom:desc', '用户名字段')
  name: string;
}

const desc = Reflect.getMetadata('custom:desc', Demo.prototype, 'name');
console.log(desc); // 用户名字段

✅ 4.2 获取属性类型(design:type

import 'reflect-metadata';

class A {
  name: string;
}

const type = Reflect.getMetadata('design:type', A.prototype, 'name');
console.log(type.name); // String

✅ 4.3 获取方法参数和返回值类型

import 'reflect-metadata';

class B {
  add(a: number, b: number): number {
    return a + b;
  }
}

console.log(Reflect.getMetadata('design:paramtypes', B.prototype, 'add')); // [ [Function: Number], [Function: Number] ]
console.log(Reflect.getMetadata('design:returntype', B.prototype, 'add')); // [Function: Number]

✅ 4.4 参数装饰器收集类型

function Log(target: Object, propertyKey: string | symbol, parameterIndex: number) {
  const types = Reflect.getMetadata('design:paramtypes', target, propertyKey);
  console.log(`参数[${parameterIndex}]类型为: ${types[parameterIndex].name}`);
}

class C {
  hello(@Log name: string) {}
}
// 输出:参数[0]类型为: String

✅ 4.5 存储方法级元数据 + 遍历

function Route(method: string) {
  return (target: Object, propertyKey: string) => {
    Reflect.defineMetadata('route', method, target, propertyKey);
  };
}

class Controller {
  @Route('GET')
  getData() {}

  @Route('POST')
  saveData() {}
}

for (const key of Object.getOwnPropertyNames(Controller.prototype)) {
  const method = Reflect.getMetadata('route', Controller.prototype, key);
  if (method) {
    console.log(`${key} => ${method}`);
  }
}
// 输出:
// getData => GET
// saveData => POST

✅ 4.6 类构造函数参数类型反射(DI 原理)

class Service {}

class Controller {
  constructor(public service: Service) {}
}

const paramTypes = Reflect.getMetadata('design:paramtypes', Controller);
console.log(paramTypes[0].name); // Service

🔄 五、继承中的元数据行为

class Parent {
  @Reflect.metadata('tag', 'parent')
  name: string;
}

class Child extends Parent {}

console.log(
  Reflect.getMetadata('tag', Child.prototype, 'name') // parent
);
console.log(
  Reflect.getOwnMetadata('tag', Child.prototype, 'name') // undefined
);

getMetadata 会递归查找继承链,getOwnMetadata 只查当前。


📦 六、实际场景总结

场景如何应用
NestJS 的 DI获取构造函数参数类型,自动实例化
权限控制方法上标记元数据(如 @Role('admin')
路由注册@Get() 装饰器存入路由表元数据
ORM 映射标记字段的类型和数据库映射
class-validator装饰器读取字段类型+校验规则

⚠️ 七、使用建议

  • 总是配合 TypeScript 装饰器使用,单独使用不推荐。
  • 开发大型框架时,reflect-metadata 是构建装饰器体系的底层核心。
  • 注意类型丢失问题,接口类型无法反射(接口编译后会被擦除)。

🏁 八、环境准备 & tsconfig 配置

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "strict": true,
    "esModuleInterop": true
  }
}
// main.ts
import 'reflect-metadata';