上篇文章,预备知识:装饰器。
reflect-metadata
是一个用于在 JavaScript 中实现反射元数据的库,主要用于 TypeScript 项目。可以为 ts 项目提供元数据 metadata
可以理解为“描述数据的数据”,比如你可以为类、方法、属性等附加一些额外的信息,然后在运行时读取这些信息来实现某些功能。它允许在运行时访问和操作类、方法、属性等的元数据,常用于依赖注入、序列化、ORM 等场景。
有很多的库中使用到了装饰器,需要依赖这个库,比如:typeorm
、inversify
等。
安装
pnpm add reflect-metadata
配置
在 tsconfig.json
中启用装饰器和元数据支持:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
API
// 在对象或属性上定义元数据
Reflect.defineMetadata(metadataKey, metadataValue, target);
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey);
// 检查元数据键是否存在于对象或属性的原型链上
let result = Reflect.hasMetadata(metadataKey, target);
let result = Reflect.hasMetadata(metadataKey, target, propertyKey);
// 检查对象或属性自身是否存在特定元数据键
let result = Reflect.hasOwnMetadata(metadataKey, target);
let result = Reflect.hasOwnMetadata(metadataKey, target, propertyKey);
// 获取对象或属性的原型链上某个元数据键的元数据值
let result = Reflect.getMetadata(metadataKey, target);
let result = Reflect.getMetadata(metadataKey, target, propertyKey);
// 获取对象或属性自身某个元数据键的元数据值
let result = Reflect.getOwnMetadata(metadataKey, target);
let result = Reflect.getOwnMetadata(metadataKey, target, propertyKey);
// 获取对象或属性原型链上的所有元数据键
let result = Reflect.getMetadataKeys(target);
let result = Reflect.getMetadataKeys(target, propertyKey);
// 获取对象或属性自身的所有元数据键
let result = Reflect.getOwnMetadataKeys(target);
let result = Reflect.getOwnMetadataKeys(target, propertyKey);
// 删除对象或属性上的元数据
let result = Reflect.deleteMetadata(metadataKey, target);
let result = Reflect.deleteMetadata(metadataKey, target, propertyKey);
// 使用装饰器为构造函数应用元数据
@Reflect.metadata(metadataKey, metadataValue)
class C {
// 使用装饰器为方法(属性)应用元数据
@Reflect.metadata(metadataKey, metadataValue)
method() {
}
}
简单使用
Reflect Metadata 的 API 可以用于类或者类的属性上,如:
import 'reflect-metadata';
class MyClass {
@Reflect.metadata('get', 'getValue')
@Reflect.metadata('post', 'postValue')
myMethod() {
console.log('myMethod');
}
}
const metadataValue = Reflect.getMetadata('get', MyClass.prototype, 'myMethod');
const metadataValue1 = Reflect.getMetadata('post', MyClass.prototype, 'myMethod');
console.log(metadataValue); // 输出: getValue
console.log(metadataValue1); // 输出: postValue
使用场景
1. 为方法附加元数据,表示它的访问权限。
import 'reflect-metadata';
function Role(role: string) {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
Reflect.defineMetadata('role', role, target, key);
};
}
class UserController {
@Role('admin')
deleteUser() {
console.log('User deleted');
}
}
// 运行时检查权限
const userController = new UserController();
const role = Reflect.getMetadata('role', userController, 'deleteUser');
if (role === 'admin') {
userController.deleteUser(); // 只有管理员可以调用
} else {
console.log('Access denied');
}
2. 依赖注入 (nest.js 的实现)
使用装饰器与 Reflect
联动,创建简易 DI 容器
import "reflect-metadata";
/** 检查目标类是否被标记为可注入 */
function validate(target: any) {
const isInjectable = Reflect.getMetadata("injectable", target);
if (!isInjectable) {
throw new Error(`${target.name} is not marked as injectable!`);
}
}
// 标记类为可注入
function Injectable() {
return function (target: any) {
Reflect.defineMetadata("injectable", true, target);
};
}
// 标记需要注入的依赖
function Inject(token: any) {
return function (target: any, _propertyKey: any, index: number) {
// 每个依赖都存储在 dependencies 数组中,索引对应参数的位置
const dependencies = Reflect.getMetadata("dependencies", target) || [];
dependencies[index] = token;
Reflect.defineMetadata("dependencies", dependencies, target);
};
}
// 简单的 DI 容器
class DIContainer {
static resolve<T extends new (...args: any[]) => any>(
target: T
): InstanceType<T> {
// 检查目标类是否被标记为可注入
validate(target);
// 获取目标类的依赖
const dependencies = Reflect.getMetadata("dependencies", target) || [];
// 创建依赖实例
const injections = dependencies.map((dep: any) => {
// 检查目标类是否被标记为可注入
validate(dep);
return new dep();
});
// 创建目标类的实例
return new target(...injections);
}
}
@Injectable()
class Service {
getMessage() {
return "Hello from Service!";
}
}
@Injectable()
class Controller {
constructor(@Inject(Service) private service: Service) {}
printMessage() {
console.log(this.service.getMessage());
}
}
const controller = DIContainer.resolve(Controller);
controller.printMessage(); // 输出:Hello from Service!
@Injectable
:标记一个类可被注入。@Inject
:在构造函数中标记依赖。DIContainer.resolve
:自动根据依赖创建实例并注入。
流程解析:
Controller
和Service
类通过@Injectable
标记为可注入。Controller
构造函数通过@Inject(Service)
指定需要Service
作为依赖。- 调用
DIContainer.resolve(Controller)
:
-
- 读取
Controller
的依赖元数据,发现它依赖Service
。 - 创建
Service
实例。 - 将
Service
实例传递给Controller
的构造函数。
- 读取
- 最终返回一个注入了
Service
的Controller
实例。
官方文档文档: rbuckton.github.io/reflect-met…