装饰器工厂与元数据
概述
装饰器工厂(Decorator Factory)是返回装饰器函数的函数,它允许我们通过参数配置装饰器的行为。装饰器组合(Decorator Composition)让我们能够将多个装饰器组合使用,实现复杂的功能。元数据(Metadata)则提供了在运行时访问类型信息的能力,是实现依赖注入、验证等高级功能的基础。掌握这些高级特性,能够让我们创建更加灵活、强大的装饰器系统。
注意
装饰器工厂和元数据功能需要启用 experimentalDecorators 和 emitDecoratorMetadata 选项。某些元数据功能还需要安装 reflect-metadata 库。
装饰器工厂深入
装饰器工厂是一个返回装饰器函数的函数,它允许我们传递参数来配置装饰器的行为。这使得装饰器更加灵活和可复用。
基本概念
装饰器工厂的基本结构:
// 装饰器工厂:接收配置参数,返回装饰器函数
function decoratorFactory(config: ConfigType) {
// 返回实际的装饰器函数
return function (target: any) {
// 使用 config 配置装饰器行为
// ...
};
}
// 使用装饰器工厂
@decoratorFactory({ option: "value" })
class MyClass {
// ...
}类装饰器工厂
类装饰器工厂可以用于创建可配置的类装饰器:
// 可配置的版本装饰器工厂
function version(versionNumber: string) {
return function <T extends { new (...args: any[]): {} }>(target: T) {
return class extends target {
static version = versionNumber;
static getVersion() {
return versionNumber;
}
};
};
}
@version("1.0.0")
class ApiService {
// ...
}
console.log(ApiService.version); // "1.0.0"
console.log(ApiService.getVersion()); // "1.0.0"方法装饰器工厂
方法装饰器工厂是最常用的装饰器工厂类型:
// 可配置的日志装饰器工厂
function log(options?: { level?: "info" | "warn" | "error"; prefix?: string }) {
const level = options?.level || "info";
const prefix = options?.prefix || "";
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const message = `${prefix}${propertyKey}(${args.join(", ")})`;
switch (level) {
case "info":
console.log(`[INFO] ${message}`);
break;
case "warn":
console.warn(`[WARN] ${message}`);
break;
case "error":
console.error(`[ERROR] ${message}`);
break;
}
return originalMethod.apply(this, args);
};
return descriptor;
};
}
class Calculator {
@log({ level: "info", prefix: "计算: " })
add(a: number, b: number): number {
return a + b;
}
@log({ level: "warn" })
subtract(a: number, b: number): number {
return a - b;
}
}
const calc = new Calculator();
calc.add(5, 3); // 输出: [INFO] 计算: add(5, 3)
calc.subtract(10, 4); // 输出: [WARN] subtract(10, 4)属性装饰器工厂
属性装饰器工厂可以用于创建可配置的属性装饰器:
// 可配置的属性验证装饰器工厂
function validate(validator: (value: any) => boolean, errorMessage?: string) {
return function (target: any, propertyKey: string) {
let value: any;
const getter = function () {
return value;
};
const setter = function (newVal: any) {
if (!validator(newVal)) {
throw new Error(errorMessage || `${propertyKey} 验证失败`);
}
value = newVal;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
};
}
class User {
@validate((v: string) => v.length >= 3, "用户名至少 3 个字符")
username: string;
@validate((v: number) => v > 0 && v < 150, "年龄必须在 0-150 之间")
age: number;
constructor(username: string, age: number) {
this.username = username;
this.age = age;
}
}
const user1 = new User("john", 30); // ✅ 成功
// const user2 = new User("jo", 30); // ❌ 错误: 用户名至少 3 个字符参数装饰器工厂
参数装饰器工厂常用于依赖注入:
// 依赖注入装饰器工厂
function inject(token: string) {
return function (target: any, propertyKey: string | undefined, parameterIndex: number) {
const metadataKey = `__inject_${propertyKey || "constructor"}`;
if (!target[metadataKey]) {
target[metadataKey] = [];
}
target[metadataKey][parameterIndex] = token;
};
}
class Service {
constructor(
@inject("DatabaseService") private db: any,
@inject("LoggerService") private logger: any
) {}
}装饰器组合
装饰器组合是指将多个装饰器应用于同一个声明,它们按照特定的顺序执行。理解装饰器的执行顺序对于正确使用装饰器组合至关重要。
执行顺序规则
装饰器的执行顺序遵循以下规则:
- 装饰器工厂函数:从上到下执行
- 装饰器函数:从下到上执行(对于类和方法装饰器)
- 参数装饰器:从最后一个参数到第一个参数
// 装饰器执行顺序示例
function first() {
console.log("1. first() 工厂函数执行");
return function (target: any) {
console.log("4. first() 装饰器应用");
};
}
function second() {
console.log("2. second() 工厂函数执行");
return function (target: any) {
console.log("3. second() 装饰器应用");
};
}
function third() {
console.log("3. third() 工厂函数执行");
return function (target: any) {
console.log("2. third() 装饰器应用");
};
}
@first()
@second()
@third()
class Example {
// ...
}
// 输出顺序:
// 1. first() 工厂函数执行
// 2. second() 工厂函数执行
// 3. third() 工厂函数执行
// 2. third() 装饰器应用
// 3. second() 装饰器应用
// 4. first() 装饰器应用实际应用:组合多个功能
在实际开发中,我们经常需要组合多个装饰器来实现复杂的功能:
// 权限检查装饰器工厂
function requirePermission(permission: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const user = (this as any).currentUser;
if (!user || !user.permissions.includes(permission)) {
throw new Error(`需要权限: ${permission}`);
}
return originalMethod.apply(this, args);
};
return descriptor;
};
}
// 缓存装饰器
function cache(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const cacheMap = new Map<string, any>();
descriptor.value = function (...args: any[]) {
const cacheKey = JSON.stringify(args);
if (cacheMap.has(cacheKey)) {
return cacheMap.get(cacheKey);
}
const result = originalMethod.apply(this, args);
cacheMap.set(cacheKey, result);
return result;
};
return descriptor;
}
// 日志装饰器工厂
function log(options?: { level?: string }) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const level = options?.level || "info";
descriptor.value = function (...args: any[]) {
console.log(`[${level.toUpperCase()}] ${propertyKey}(${args.join(", ")})`);
return originalMethod.apply(this, args);
};
return descriptor;
};
}
// 组合使用多个装饰器
class ApiController {
currentUser: { permissions: string[] } | null = null;
// 执行顺序:权限检查 -> 缓存 -> 日志 -> 实际方法
@requirePermission("read")
@cache
@log({ level: "info" })
async getData(id: string) {
return { id, data: "some data" };
}
// 执行顺序:权限检查 -> 验证 -> 日志 -> 实际方法
@requirePermission("write")
@log({ level: "warn" })
async updateData(id: string, data: any) {
return { id, data, updated: true };
}
}创建装饰器组合工具
我们可以创建工具函数来简化装饰器组合:
// 装饰器组合工具函数
function composeDecorators(...decorators: Array<Function>) {
return function (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) {
// 从最后一个装饰器开始应用
for (let i = decorators.length - 1; i >= 0; i--) {
const decorator = decorators[i];
if (propertyKey && descriptor) {
// 方法装饰器
const result = decorator(target, propertyKey, descriptor);
if (result) {
descriptor = result;
}
} else {
// 类装饰器
const result = decorator(target);
if (result) {
target = result;
}
}
}
return descriptor || target;
};
}
// 使用组合工具
class Service {
@composeDecorators(
log({ level: "info" }),
cache,
requirePermission("read")
)
getData(id: string) {
return { id, data: "data" };
}
}元数据(Metadata)
元数据允许我们在运行时访问类型信息,这对于实现依赖注入、验证、序列化等高级功能至关重要。TypeScript 通过 emitDecoratorMetadata 选项和 reflect-metadata 库来支持元数据。
启用元数据支持
首先需要在 tsconfig.json 中启用元数据:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}然后安装 reflect-metadata 库:
npm install reflect-metadata在应用入口导入:
import "reflect-metadata";基本元数据操作
reflect-metadata 提供了基本的元数据操作:
import "reflect-metadata";
// 定义元数据
class MyClass {
@Reflect.metadata("key", "value")
property: string;
}
// 获取元数据
const value = Reflect.getMetadata("key", MyClass.prototype, "property");
console.log(value); // "value"
// 检查是否有元数据
const hasMetadata = Reflect.hasMetadata("key", MyClass.prototype, "property");
console.log(hasMetadata); // true
// 获取所有元数据键
const keys = Reflect.getMetadataKeys(MyClass.prototype, "property");
console.log(keys); // ["key"]
// 删除元数据
Reflect.deleteMetadata("key", MyClass.prototype, "property");类型元数据
启用 emitDecoratorMetadata 后,TypeScript 会自动为装饰的参数、属性和方法生成类型元数据:
import "reflect-metadata";
// 类型元数据的键
const designType = "design:type";
const designParamTypes = "design:paramtypes";
const designReturnType = "design:returntype";
class Service {
// 属性类型元数据
@Reflect.metadata(designType, String)
name: string;
// 方法参数类型元数据
process(
@Reflect.metadata(designParamTypes, [String, Number])
data: string,
count: number
): void {
// ...
}
}
// 获取类型元数据
const nameType = Reflect.getMetadata(designType, Service.prototype, "name");
console.log(nameType); // [Function: String]
const paramTypes = Reflect.getMetadata(designParamTypes, Service.prototype, "process");
console.log(paramTypes); // [Function: String, Function: Number]依赖注入实现
使用元数据实现完整的依赖注入系统:
import "reflect-metadata";
// 依赖注入标记
const INJECT_TOKEN = Symbol("inject");
// 可注入装饰器
function Injectable(token?: string) {
return function <T extends { new (...args: any[]): {} }>(target: T) {
const tokenValue = token || target.name;
Reflect.defineMetadata(INJECT_TOKEN, tokenValue, target);
return target;
};
}
// 参数注入装饰器
function Inject(token: string) {
return function (target: any, propertyKey: string | undefined, parameterIndex: number) {
const paramTypes = Reflect.getMetadata("design:paramtypes", target, propertyKey || "constructor");
const injectionTokens = Reflect.getMetadata(INJECT_TOKEN, target) || {};
if (!injectionTokens[propertyKey || "constructor"]) {
injectionTokens[propertyKey || "constructor"] = [];
}
injectionTokens[propertyKey || "constructor"][parameterIndex] = token;
Reflect.defineMetadata(INJECT_TOKEN, injectionTokens, target);
};
}
// 依赖注入容器
class Container {
private services = new Map<string, any>();
register<T>(token: string, service: T): void {
this.services.set(token, service);
}
resolve<T>(target: new (...args: any[]) => T): T {
const injections = Reflect.getMetadata(INJECT_TOKEN, target) || {};
const constructorInjections = injections["constructor"] || [];
const paramTypes = Reflect.getMetadata("design:paramtypes", target) || [];
const args = constructorInjections.map((token: string, index: number) => {
if (token) {
return this.services.get(token);
}
// 如果没有指定 token,尝试使用类型
const paramType = paramTypes[index];
if (paramType && paramType !== Object) {
const typeToken = Reflect.getMetadata(INJECT_TOKEN, paramType) || paramType.name;
return this.services.get(typeToken);
}
return undefined;
});
return new target(...args);
}
}
// 使用示例
@Injectable("DatabaseService")
class DatabaseService {
connect() {
return "数据库已连接";
}
}
@Injectable("LoggerService")
class LoggerService {
log(message: string) {
console.log(`[LOG] ${message}`);
}
}
@Injectable("UserService")
class UserService {
constructor(
@Inject("DatabaseService") private db: DatabaseService,
@Inject("LoggerService") private logger: LoggerService
) {}
getUsers() {
this.logger.log("获取用户列表");
return this.db.connect() + " - 用户列表";
}
}
// 注册服务
const container = new Container();
container.register("DatabaseService", new DatabaseService());
container.register("LoggerService", new LoggerService());
// 解析服务
const userService = container.resolve(UserService);
console.log(userService.getUsers());
// 输出: [LOG] 获取用户列表
// 输出: 数据库已连接 - 用户列表验证系统实现
使用元数据实现类型验证系统:
import "reflect-metadata";
// 验证装饰器工厂
function Validate(validator: (value: any) => boolean, errorMessage?: string) {
return function (target: any, propertyKey: string) {
const validators = Reflect.getMetadata("validators", target) || {};
validators[propertyKey] = { validator, errorMessage };
Reflect.defineMetadata("validators", validators, target);
};
}
// 验证方法装饰器
function ValidateClass(target: any) {
const validators = Reflect.getMetadata("validators", target.prototype) || {};
return class extends target {
constructor(...args: any[]) {
super(...args);
// 验证所有标记的属性
for (const [propertyKey, { validator, errorMessage }] of Object.entries(validators)) {
const value = (this as any)[propertyKey];
if (!validator(value)) {
throw new Error(errorMessage || `${propertyKey} 验证失败`);
}
}
}
};
}
@ValidateClass
class User {
@Validate((v: string) => v.length >= 3, "用户名至少 3 个字符")
username: string;
@Validate((v: number) => v > 0 && v < 150, "年龄必须在 0-150 之间")
age: number;
@Validate((v: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v), "邮箱格式不正确")
email: string;
constructor(username: string, age: number, email: string) {
this.username = username;
this.age = age;
this.email = email;
}
}
const user1 = new User("john", 30, "john@example.com"); // ✅ 成功
// const user2 = new User("jo", 30, "john@example.com"); // ❌ 错误: 用户名至少 3 个字符序列化系统实现
使用元数据实现对象序列化:
import "reflect-metadata";
// 序列化选项
interface SerializeOptions {
name?: string;
optional?: boolean;
transform?: (value: any) => any;
}
// 序列化装饰器工厂
function Serialize(options?: SerializeOptions) {
return function (target: any, propertyKey: string) {
const serializers = Reflect.getMetadata("serializers", target) || {};
serializers[propertyKey] = options || {};
Reflect.defineMetadata("serializers", serializers, target);
};
}
// 序列化工具类
class Serializer {
static serialize<T>(instance: T): any {
const serializers = Reflect.getMetadata("serializers", instance.constructor.prototype) || {};
const result: any = {};
for (const [propertyKey, options] of Object.entries(serializers)) {
const value = (instance as any)[propertyKey];
const opts = options as SerializeOptions;
// 跳过可选且为 undefined 的属性
if (opts.optional && value === undefined) {
continue;
}
// 应用转换函数
const serializedValue = opts.transform ? opts.transform(value) : value;
// 使用自定义名称或原属性名
const key = opts.name || propertyKey;
result[key] = serializedValue;
}
return result;
}
}
// 使用示例
class Product {
@Serialize({ name: "product_name" })
name: string;
@Serialize({ transform: (v: number) => `$${v.toFixed(2)}` })
price: number;
@Serialize({ optional: true })
description?: string;
constructor(name: string, price: number, description?: string) {
this.name = name;
this.price = price;
this.description = description;
}
}
const product = new Product("Laptop", 999.99, "高性能笔记本");
const serialized = Serializer.serialize(product);
console.log(serialized);
// { product_name: "Laptop", price: "$999.99", description: "高性能笔记本" }高级应用场景
场景 1:ORM 实体定义
使用装饰器和元数据定义数据库实体:
import "reflect-metadata";
// 列装饰器工厂
function Column(options?: { type?: string; nullable?: boolean; primaryKey?: boolean }) {
return function (target: any, propertyKey: string) {
const columns = Reflect.getMetadata("columns", target) || {};
columns[propertyKey] = {
type: options?.type || "string",
nullable: options?.nullable || false,
primaryKey: options?.primaryKey || false,
...options
};
Reflect.defineMetadata("columns", columns, target);
};
}
// 表装饰器工厂
function Table(tableName: string) {
return function <T extends { new (...args: any[]): {} }>(target: T) {
Reflect.defineMetadata("table", tableName, target);
return target;
};
}
@Table("users")
class User {
@Column({ type: "number", primaryKey: true })
id: number;
@Column({ type: "string" })
name: string;
@Column({ type: "string", nullable: true })
email?: string;
@Column({ type: "date" })
createdAt: Date;
}
// 获取表信息
const tableName = Reflect.getMetadata("table", User);
const columns = Reflect.getMetadata("columns", User.prototype);
console.log(tableName); // "users"
console.log(columns);
// {
// id: { type: "number", primaryKey: true, nullable: false },
// name: { type: "string", nullable: false },
// email: { type: "string", nullable: true },
// createdAt: { type: "date", nullable: false }
// }场景 2:路由装饰器系统
创建类似 Express 或 NestJS 的路由装饰器系统:
import "reflect-metadata";
// HTTP 方法装饰器工厂
function createHttpMethodDecorator(method: string) {
return function (path: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const routes = Reflect.getMetadata("routes", target) || [];
routes.push({
method,
path,
handler: propertyKey
});
Reflect.defineMetadata("routes", routes, target);
};
};
}
// 便捷方法
const Get = createHttpMethodDecorator("GET");
const Post = createHttpMethodDecorator("POST");
const Put = createHttpMethodDecorator("PUT");
const Delete = createHttpMethodDecorator("DELETE");
// 控制器装饰器
function Controller(basePath: string) {
return function <T extends { new (...args: any[]): {} }>(target: T) {
Reflect.defineMetadata("basePath", basePath, target);
return target;
};
}
@Controller("/api/users")
class UserController {
@Get("/")
getAllUsers() {
return { users: [] };
}
@Get("/:id")
getUserById(id: string) {
return { id, user: {} };
}
@Post("/")
createUser(data: any) {
return { created: true, data };
}
@Put("/:id")
updateUser(id: string, data: any) {
return { updated: true, id, data };
}
@Delete("/:id")
deleteUser(id: string) {
return { deleted: true, id };
}
}
// 获取路由信息
const basePath = Reflect.getMetadata("basePath", UserController);
const routes = Reflect.getMetadata("routes", UserController.prototype);
console.log(basePath); // "/api/users"
console.log(routes);
// [
// { method: "GET", path: "/", handler: "getAllUsers" },
// { method: "GET", path: "/:id", handler: "getUserById" },
// { method: "POST", path: "/", handler: "createUser" },
// { method: "PUT", path: "/:id", handler: "updateUser" },
// { method: "DELETE", path: "/:id", handler: "deleteUser" }
// ]注意事项
注意
性能影响:元数据会增加代码体积和运行时开销,特别是在大量使用的情况下。在生产环境中需要权衡。
类型安全:元数据操作在编译时无法完全验证,需要开发者自行确保类型安全。
库依赖:使用元数据功能需要安装
reflect-metadata库,这会增加项目依赖。浏览器兼容性:
reflect-metadata使用了一些较新的 JavaScript 特性,可能需要 polyfill 支持。执行顺序:装饰器组合的执行顺序可能影响最终行为,需要仔细理解执行顺序规则。
元数据键冲突:使用自定义元数据键时,需要注意避免键名冲突,建议使用 Symbol 或命名空间。
最佳实践
使用 Symbol 作为元数据键:避免键名冲突,提高代码安全性。
封装元数据操作:创建工具函数封装常用的元数据操作,提高代码复用性。
文档化装饰器:为自定义装饰器添加清晰的文档,说明参数、行为和元数据使用。
类型定义:为装饰器工厂提供准确的类型定义,提高类型安全性。
测试覆盖:充分测试装饰器组合和元数据功能,确保行为符合预期。
性能监控:在性能敏感的场景中,监控装饰器和元数据的使用对性能的影响。
渐进式采用:从简单的装饰器工厂开始,逐步增加复杂度,避免过度设计。
相关链接
- 装饰器基础 - 装饰器的基本概念和语法
- 类装饰器详解 - 各种装饰器的详细用法
- 类基础 - 了解类的基本概念
- 函数类型 - 理解函数类型,有助于理解装饰器的函数签名
- TypeScript 官方文档 - 装饰器
- reflect-metadata 文档