TypeScript 装饰器工厂与元数据反射
装饰器工厂与元数据反射是高级装饰器用法,实现依赖注入与 AOP 编程,下面梳理核心用法。
装饰器工厂
装饰器工厂是返回装饰器函数的函数,支持传入配置参数:
TypeScript
function Route(path: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// 存储路由信息
Reflect.defineMetadata("route:path", path, target, propertyKey);
};
}
class ApiController {
@Route("/api/users")
getUsers() {
return [];
}
}
多参数工厂
TypeScript
function Validate(required: boolean, type: "string" | "number") {
return function (target: any, propertyKey: string, parameterIndex: number) {
const key = `validate:${propertyKey}:${parameterIndex}`;
Reflect.defineMetadata(key, { required, type }, target);
};
}
class UserService {
createUser(@Validate(true, "string") name: string) {}
}
装饰器组合
同一目标可应用多个装饰器,按顺序执行:
TypeScript
function Log(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`>> ${key}`);
const result = original.apply(this, args);
console.log(`<< ${key}`);
return result;
};
}
function Cache(ttl: number) {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
const cache = new Map();
descriptor.value = function (...args: any[]) {
const cacheKey = JSON.stringify(args);
if (cache.has(cacheKey)) return cache.get(cacheKey);
const result = original.apply(this, args);
cache.set(cacheKey, result);
setTimeout(() => cache.delete(cacheKey), ttl);
return result;
};
};
}
class DataService {
@Log
@Cache(5000)
fetchData(id: string) {
return { id, data: "result" };
}
}
// 执行顺序:Cache 包装 Log 包装 原方法
// 调用:Log >> Cache >> 原方法 >> Cache >> Log <<
注意:装饰器从下到上执行,最靠近方法的先执行。
元数据反射
使用 reflect-metadata 库存储与读取装饰器元数据:
安装
Bash
npm install reflect-metadata
基本用法
TypeScript
import "reflect-metadata";
const ROUTE_KEY = "design:route";
function Get(path: string) {
return function (target: any, propertyKey: string) {
Reflect.defineMetadata(ROUTE_KEY, path, target, propertyKey);
};
}
function Post(path: string) {
return function (target: any, propertyKey: string) {
Reflect.defineMetadata(ROUTE_KEY, `POST:${path}`, target, propertyKey);
};
}
class UserController {
@Get("/users")
list() {}
@Post("/users")
create() {}
}
// 读取元数据
const path = Reflect.getMetadata(ROUTE_KEY, new UserController(), "list");
console.log(path); // "/users"
设计时类型元数据
TypeScript
function LogType(target: any, propertyKey: string) {
const type = Reflect.getMetadata("design:type", target, propertyKey);
console.log(`${propertyKey} type: ${type.name}`);
}
class User {
@LogType
name!: string; // 输出:name type: String
}
| 元数据键 | 说明 |
|---|---|
design:type | 属性/参数类型 |
design:paramtypes | 方法参数类型列表 |
design:returntype | 方法返回值类型 |
依赖注入示例
TypeScript
const INJECT_KEY = "inject";
function Inject(token: any) {
return function (target: any, propertyKey: string, parameterIndex: number) {
const params = Reflect.getMetadata(INJECT_KEY, target) || {};
params[parameterIndex] = token;
Reflect.defineMetadata(INJECT_KEY, params, target);
};
}
function createInstance<T>(ctor: new (...args: any[]) => T): T {
const params = Reflect.getMetadata(INJECT_KEY, ctor) || {};
const args = Object.keys(params).map(i => new (params[i])());
return new ctor(...args);
}
class Database {}
class UserRepository {
constructor(@Inject(Database) private db: Database) {}
}
const repo = createInstance(UserRepository);
要点总结
- 装饰器工厂返回装饰器函数,支持传入配置参数定制行为
- 多个装饰器可组合应用,执行顺序从下到上
reflect-metadata提供运行时元数据存储与读取design:type/design:paramtypes/design:returntype获取设计时类型信息- 依赖注入通过装饰器记录参数类型,运行时自动实例化
📝 发现内容有误?点击此处直接编辑