|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
TypeScript装饰器是一种特殊类型的声明,它可以附加到类声明、方法、访问符、属性或参数上。装饰器使用@expression这种形式,其中expression必须求值为一个函数,该函数会在运行时被调用,被装饰的声明信息作为参数传入。
装饰器是TypeScript中的一项实验性特性,它受到了Python中装饰器的启发,并在Angular、NestJS等现代框架中得到了广泛应用。通过装饰器,我们可以在不修改原有代码的情况下,动态地扩展类的功能,实现代码的复用和关注点分离。
装饰器基础
在TypeScript中,装饰器本质上是一个函数,它接收目标对象(被装饰的元素)作为参数,并可以对其进行修改或扩展。装饰器可以在编译时或运行时执行,这取决于装饰器的类型和配置。
要启用装饰器支持,你需要在tsconfig.json文件中设置experimentalDecorators选项为true:
- {
- "compilerOptions": {
- "experimentalDecorators": true,
- "emitDecoratorMetadata": true
- }
- }
复制代码
装饰器的基本语法如下:
- function simpleDecorator(target: any) {
- // 装饰器逻辑
- console.log("装饰器被调用");
- console.log("目标对象:", target);
- }
- @simpleDecorator
- class MyClass {
- // 类定义
- }
复制代码
类装饰器
类装饰器应用于类构造函数,可以用来监视、修改或替换类定义。类装饰器在类声明时执行,而不是在类实例化时执行。
类装饰器的函数签名如下:
- function classDecorator<T extends { new(...args: any[]): {} }>(constructor: T) {
- return class extends constructor {
- // 新的类定义
- }
- }
复制代码
下面是一个简单的类装饰器示例:
- function sealed(constructor: Function) {
- Object.seal(constructor);
- Object.seal(constructor.prototype);
- console.log("类已被密封");
- }
- @sealed
- class BugReport {
- type = "report";
- title: string;
- constructor(t: string) {
- this.title = t;
- }
- }
- // 尝试添加新属性将会失败
- // BugReport.prototype.newProperty = "test"; // 在严格模式下会报错
复制代码
更复杂的类装饰器可以修改或扩展类的行为:
- function logClass<T extends { new(...args: any[]): {} }>(constructor: T) {
- return class extends constructor {
- // 创建新的构造函数
- constructor(...args: any[]) {
- super(...args);
- console.log(`创建 ${constructor.name} 实例,参数: ${args.join(", ")}`);
- }
- }
- }
- @logClass
- class User {
- name: string;
- age: number;
- constructor(name: string, age: number) {
- this.name = name;
- this.age = age;
- }
- greet() {
- console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
- }
- }
- const user = new User("Alice", 30);
- // 输出: 创建 User 实例,参数: Alice, 30
- user.greet();
- // 输出: Hello, my name is Alice and I'm 30 years old.
复制代码
方法装饰器
方法装饰器应用于方法的属性描述符,可以用来监视、修改或替换方法定义。方法装饰器在运行时接收三个参数:
1. 对于静态成员,是类的构造函数;对于实例成员,是类的原型
2. 成员的名称
3. 成员的属性描述符
方法装饰器的函数签名如下:
- function methodDecorator(
- target: Object,
- propertyKey: string | symbol,
- descriptor: PropertyDescriptor
- ) {
- // 装饰器逻辑
- }
复制代码
下面是一个简单的方法装饰器示例:
- function log(
- target: Object,
- propertyKey: string | symbol,
- descriptor: PropertyDescriptor
- ) {
- const originalMethod = descriptor.value;
-
- descriptor.value = function(...args: any[]) {
- console.log(`调用方法 ${propertyKey.toString()},参数: ${JSON.stringify(args)}`);
- const result = originalMethod.apply(this, args);
- console.log(`方法 ${propertyKey.toString()} 返回: ${JSON.stringify(result)}`);
- return result;
- };
-
- return descriptor;
- }
- class Calculator {
- @log
- add(a: number, b: number): number {
- return a + b;
- }
- }
- const calculator = new Calculator();
- const result = calculator.add(2, 3);
- // 输出:
- // 调用方法 add,参数: [2,3]
- // 方法 add 返回: 5
复制代码
方法装饰器也可以用于修改方法的访问权限:
- function readonly(
- target: Object,
- propertyKey: string | symbol,
- descriptor: PropertyDescriptor
- ) {
- descriptor.writable = false;
- return descriptor;
- }
- class Employee {
- private _salary: number = 1000;
-
- @readonly
- get salary(): number {
- return this._salary;
- }
- }
- const employee = new Employee();
- console.log(employee.salary); // 输出: 1000
- // 尝试修改getter将会失败
- // employee.salary = 2000; // 在严格模式下会报错
复制代码
属性装饰器
属性装饰器应用于类的属性,可以用来监视或修改属性的定义。属性装饰器在运行时接收两个参数:
1. 对于静态成员,是类的构造函数;对于实例成员,是类的原型
2. 属性的名称
属性装饰器的函数签名如下:
- function propertyDecorator(
- target: Object,
- propertyKey: string | symbol
- ) {
- // 装饰器逻辑
- }
复制代码
下面是一个简单的属性装饰器示例:
- function format(formatString: string) {
- return function (target: Object, propertyKey: string | symbol) {
- let value: string;
-
- const getter = function() {
- return `${formatString} ${value} ${formatString}`;
- };
-
- const setter = function(newVal: string) {
- value = newVal;
- };
-
- Object.defineProperty(target, propertyKey, {
- get: getter,
- set: setter,
- enumerable: true,
- configurable: true
- });
- };
- }
- class Greeter {
- @format("***")
- greeting: string;
- }
- const greeter = new Greeter();
- greeter.greeting = "Hello";
- console.log(greeter.greeting); // 输出: *** Hello ***
复制代码
属性装饰器也可以用于验证属性值:
- function validateEmail(target: Object, propertyKey: string | symbol) {
- let value: string;
-
- const getter = function() {
- return value;
- };
-
- const setter = function(newVal: string) {
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
- if (!emailRegex.test(newVal)) {
- throw new Error("Invalid email format");
- }
- value = newVal;
- };
-
- Object.defineProperty(target, propertyKey, {
- get: getter,
- set: setter,
- enumerable: true,
- configurable: true
- });
- }
- class User {
- @validateEmail
- email: string;
-
- constructor(email: string) {
- this.email = email;
- }
- }
- const user1 = new User("test@example.com"); // 正确
- console.log(user1.email); // 输出: test@example.com
- // const user2 = new User("invalid-email"); // 抛出错误: Invalid email format
复制代码
参数装饰器
参数装饰器应用于方法的参数,可以用来监视方法的参数。参数装饰器在运行时接收三个参数:
1. 对于静态成员,是类的构造函数;对于实例成员,是类的原型
2. 方法的名称
3. 参数在函数参数列表中的索引
参数装饰器的函数签名如下:
- function parameterDecorator(
- target: Object,
- propertyKey: string | symbol,
- parameterIndex: number
- ) {
- // 装饰器逻辑
- }
复制代码
下面是一个简单的参数装饰器示例:
- function logParameter(
- target: Object,
- propertyKey: string | symbol,
- parameterIndex: number
- ) {
- console.log(`参数装饰器被调用`);
- console.log(`目标对象:`, target);
- console.log(`方法名:`, propertyKey.toString());
- console.log(`参数索引:`, parameterIndex);
- }
- class UserService {
- createUser(
- @logParameter name: string,
- @logParameter age: number,
- email: string
- ) {
- console.log(`创建用户: ${name}, ${age}, ${email}`);
- }
- }
- const userService = new UserService();
- userService.createUser("Alice", 30, "alice@example.com");
复制代码
参数装饰器通常用于依赖注入或参数验证。下面是一个参数验证的示例:
- function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
- const methodName = propertyKey.toString();
- const requiredParameters: number[] = Reflect.getOwnMetadata("required", target, methodName) || [];
- requiredParameters.push(parameterIndex);
- Reflect.defineMetadata("required", requiredParameters, target, methodName);
- }
- function validate(target: any, propertyName: string, descriptor: PropertyDescriptor) {
- const method = descriptor.value;
-
- descriptor.value = function(...args: any[]) {
- const requiredParameters: number[] = Reflect.getOwnMetadata("required", target, propertyName) || [];
-
- for (const parameterIndex of requiredParameters) {
- if (args[parameterIndex] === undefined || args[parameterIndex] === null) {
- throw new Error(`参数 ${parameterIndex} 是必需的`);
- }
- }
-
- return method.apply(this, args);
- };
- }
- class OrderService {
- @validate
- createOrder(
- @required productId: string,
- @required quantity: number,
- discount?: number
- ) {
- console.log(`创建订单: 产品ID=${productId}, 数量=${quantity}, 折扣=${discount || 0}`);
- }
- }
- const orderService = new OrderService();
- orderService.createOrder("P123", 2); // 正确
- // orderService.createOrder("P123"); // 抛出错误: 参数 1 是必需的
复制代码
装饰器工厂
装饰器工厂是一个函数,它返回一个装饰器函数。装饰器工厂允许我们自定义装饰器的行为,通过传递参数来配置装饰器。
装饰器工厂的基本语法如下:
- function decoratorFactory(config: any) {
- return function (target: any) {
- // 装饰器逻辑,可以使用config
- }
- }
- @decoratorFactory({ option1: true, option2: "value" })
- class MyClass {
- // 类定义
- }
复制代码
下面是一个装饰器工厂的示例:
- function setAPIVersion(version: string) {
- return function (constructor: Function) {
- constructor.prototype.apiVersion = version;
- };
- }
- @setAPIVersion("1.0.0")
- class API {
- // 类定义
- }
- const api = new API();
- console.log((api as any).apiVersion); // 输出: 1.0.0
复制代码
装饰器工厂也可以用于方法装饰器:
- function logMethod(prefix: string) {
- return function (
- target: Object,
- propertyKey: string | symbol,
- descriptor: PropertyDescriptor
- ) {
- const originalMethod = descriptor.value;
-
- descriptor.value = function(...args: any[]) {
- console.log(`${prefix} - 调用方法 ${propertyKey.toString()}`);
- const result = originalMethod.apply(this, args);
- console.log(`${prefix} - 方法 ${propertyKey.toString()} 完成`);
- return result;
- };
-
- return descriptor;
- };
- }
- class DataService {
- @logMethod("[Data Service]")
- fetchData(id: string) {
- console.log(`获取数据,ID: ${id}`);
- return { id, data: "示例数据" };
- }
- }
- const dataService = new DataService();
- const data = dataService.fetchData("123");
- // 输出:
- // [Data Service] - 调用方法 fetchData
- // 获取数据,ID: 123
- // [Data Service] - 方法 fetchData 完成
复制代码
实际应用场景
装饰器在实际开发中有许多应用场景,下面介绍几个常见的例子。
1. 日志记录
装饰器可以用于自动记录方法的调用和返回值:
- function log(
- target: Object,
- propertyKey: string | symbol,
- descriptor: PropertyDescriptor
- ) {
- const originalMethod = descriptor.value;
-
- descriptor.value = function(...args: any[]) {
- const start = Date.now();
- console.log(`[${new Date().toISOString()}] 调用方法 ${propertyKey.toString()},参数: ${JSON.stringify(args)}`);
-
- try {
- const result = originalMethod.apply(this, args);
- const duration = Date.now() - start;
- console.log(`[${new Date().toISOString()}] 方法 ${propertyKey.toString()} 成功完成,耗时: ${duration}ms`);
- return result;
- } catch (error) {
- const duration = Date.now() - start;
- console.error(`[${new Date().toISOString()}] 方法 ${propertyKey.toString()} 抛出异常,耗时: ${duration}ms,错误: ${error}`);
- throw error;
- }
- };
-
- return descriptor;
- }
- class ProductService {
- @log
- getProduct(id: string) {
- // 模拟数据库查询
- if (id === "invalid") {
- throw new Error("产品不存在");
- }
- return { id, name: "示例产品", price: 99.99 };
- }
- }
- const productService = new ProductService();
- productService.getProduct("123"); // 记录成功调用
- try {
- productService.getProduct("invalid"); // 记录异常
- } catch (error) {
- console.log("捕获到异常:", error.message);
- }
复制代码
2. 权限控制
装饰器可以用于检查用户权限:
- enum Role {
- ADMIN = "admin",
- USER = "user",
- GUEST = "guest"
- }
- function hasRole(role: Role) {
- return function (
- target: Object,
- propertyKey: string | symbol,
- descriptor: PropertyDescriptor
- ) {
- const originalMethod = descriptor.value;
-
- descriptor.value = function(...args: any[]) {
- // 假设用户角色存储在实例的userRole属性中
- const userRole = (this as any).userRole;
-
- if (userRole !== role) {
- throw new Error(`权限不足,需要 ${role} 权限`);
- }
-
- return originalMethod.apply(this, args);
- };
-
- return descriptor;
- };
- }
- class AdminService {
- userRole: Role;
-
- constructor(role: Role) {
- this.userRole = role;
- }
-
- @hasRole(Role.ADMIN)
- deleteUser(userId: string) {
- console.log(`删除用户: ${userId}`);
- return { success: true };
- }
- }
- const adminService = new AdminService(Role.ADMIN);
- adminService.deleteUser("123"); // 成功
- const userService = new AdminService(Role.USER);
- try {
- userService.deleteUser("456"); // 抛出错误: 权限不足,需要 admin 权限
- } catch (error) {
- console.log("捕获到异常:", error.message);
- }
复制代码
3. 性能监控
装饰器可以用于监控方法的执行时间:
- function measure(
- target: Object,
- propertyKey: string | symbol,
- descriptor: PropertyDescriptor
- ) {
- const originalMethod = descriptor.value;
-
- descriptor.value = function(...args: any[]) {
- const start = performance.now();
- const result = originalMethod.apply(this, args);
- const end = performance.now();
- const duration = end - start;
-
- console.log(`方法 ${propertyKey.toString()} 执行时间: ${duration.toFixed(2)}ms`);
-
- // 如果方法返回Promise,等待Promise完成
- if (result && typeof result.then === 'function') {
- return result.then((res: any) => {
- const asyncEnd = performance.now();
- const asyncDuration = asyncEnd - start;
- console.log(`方法 ${propertyKey.toString()} 总执行时间 (异步): ${asyncDuration.toFixed(2)}ms`);
- return res;
- });
- }
-
- return result;
- };
-
- return descriptor;
- }
- class DataProcessor {
- @measure
- processSync(data: any[]) {
- // 模拟同步处理
- let result = 0;
- for (let i = 0; i < 1000000; i++) {
- result += Math.sqrt(i);
- }
- return { processed: true, count: data.length };
- }
-
- @measure
- async processAsync(data: any[]) {
- // 模拟异步处理
- return new Promise(resolve => {
- setTimeout(() => {
- resolve({ processed: true, count: data.length });
- }, 100);
- });
- }
- }
- const processor = new DataProcessor();
- processor.processSync([1, 2, 3]);
- processor.processAsync([4, 5, 6]);
复制代码
4. 缓存
装饰器可以用于缓存方法的结果,避免重复计算:
- function cache(
- target: Object,
- propertyKey: string | symbol,
- descriptor: PropertyDescriptor
- ) {
- const originalMethod = descriptor.value;
- const cache = new Map<string, any>();
-
- descriptor.value = function(...args: any[]) {
- const key = JSON.stringify(args);
-
- if (cache.has(key)) {
- console.log(`从缓存中获取 ${propertyKey.toString()}(${key})`);
- return cache.get(key);
- }
-
- console.log(`计算 ${propertyKey.toString()}(${key})`);
- const result = originalMethod.apply(this, args);
- cache.set(key, result);
- return result;
- };
-
- return descriptor;
- }
- class MathService {
- @cache
- fibonacci(n: number): number {
- if (n <= 1) {
- return n;
- }
- return this.fibonacci(n - 1) + this.fibonacci(n - 2);
- }
- }
- const mathService = new MathService();
- console.log(mathService.fibonacci(10)); // 计算并缓存
- console.log(mathService.fibonacci(10)); // 从缓存中获取
- console.log(mathService.fibonacci(15)); // 计算并缓存
- console.log(mathService.fibonacci(10)); // 从缓存中获取
复制代码
5. 防抖和节流
装饰器可以用于实现防抖和节流功能:
- function debounce(delay: number) {
- return function (
- target: Object,
- propertyKey: string | symbol,
- descriptor: PropertyDescriptor
- ) {
- const originalMethod = descriptor.value;
- let timeoutId: NodeJS.Timeout | null = null;
-
- descriptor.value = function(...args: any[]) {
- if (timeoutId) {
- clearTimeout(timeoutId);
- }
-
- timeoutId = setTimeout(() => {
- originalMethod.apply(this, args);
- timeoutId = null;
- }, delay);
- };
-
- return descriptor;
- };
- }
- function throttle(delay: number) {
- return function (
- target: Object,
- propertyKey: string | symbol,
- descriptor: PropertyDescriptor
- ) {
- const originalMethod = descriptor.value;
- let lastCall = 0;
-
- descriptor.value = function(...args: any[]) {
- const now = Date.now();
-
- if (now - lastCall >= delay) {
- lastCall = now;
- originalMethod.apply(this, args);
- }
- };
-
- return descriptor;
- };
- }
- class SearchService {
- @debounce(300)
- search(query: string) {
- console.log(`搜索: ${query}`);
- // 实际应用中,这里会调用API进行搜索
- }
-
- @throttle(1000)
- logActivity(activity: string) {
- console.log(`记录活动: ${activity}`);
- // 实际应用中,这里会发送日志到服务器
- }
- }
- const searchService = new SearchService();
- searchService.search("a");
- searchService.search("ap");
- searchService.search("app");
- searchService.search("appl");
- searchService.search("apple"); // 只有最后一次搜索会执行
- const activityService = new SearchService();
- activityService.logActivity("点击按钮");
- activityService.logActivity("滚动页面");
- activityService.logActivity("输入文本"); // 只有第一次和最后一次活动会记录
复制代码
装饰器元数据
装饰器元数据是TypeScript装饰器的一个高级特性,它允许我们在装饰器中存储和检索额外的信息。要使用元数据,需要安装reflect-metadata库并在应用中导入它:
- npm install reflect-metadata
复制代码
然后在应用的入口文件中导入:
- import "reflect-metadata";
复制代码
元数据API提供了几个方法来操作元数据:
• Reflect.defineMetadata(metadataKey, metadataValue, target)
• Reflect.getMetadata(metadataKey, target)
• Reflect.hasMetadata(metadataKey, target)
• Reflect.deleteMetadata(metadataKey, target)
• Reflect.getMetadataKeys(target)
• Reflect.getOwnMetadata(metadataKey, target)
• Reflect.getOwnMetadataKeys(target)
下面是一个使用元数据的示例:
- function classDecorator(constructor: Function) {
- Reflect.defineMetadata("class:version", "1.0.0", constructor);
- }
- function methodDecorator(target: Object, propertyKey: string | symbol) {
- Reflect.defineMetadata("method:permission", "admin", target, propertyKey);
- }
- @classDecorator
- class UserService {
- @methodDecorator
- createUser() {
- console.log("创建用户");
- }
- }
- // 获取类元数据
- const version = Reflect.getMetadata("class:version", UserService);
- console.log(`类版本: ${version}`); // 输出: 类版本: 1.0.0
- // 获取方法元数据
- const permission = Reflect.getMetadata("method:permission", UserService.prototype, "createUser");
- console.log(`方法权限: ${permission}`); // 输出: 方法权限: admin
复制代码
元数据在依赖注入框架中特别有用,例如:
- function Injectable() {
- return function (constructor: Function) {
- Reflect.defineMetadata("injectable", true, constructor);
- };
- }
- function Inject(token: any) {
- return function (target: Object, propertyKey: string | symbol) {
- Reflect.defineMetadata("inject:token", token, target, propertyKey);
- };
- }
- @Injectable()
- class DatabaseService {
- query(sql: string) {
- console.log(`执行查询: ${sql}`);
- return [{ id: 1, name: "示例数据" }];
- }
- }
- @Injectable()
- class UserService {
- @Inject(DatabaseService)
- private database!: DatabaseService;
-
- getUsers() {
- return this.database.query("SELECT * FROM users");
- }
- }
- // 模拟依赖注入容器
- class Container {
- private services = new Map<string, any>();
-
- register<T>(token: new (...args: any[]) => T, instance: T) {
- this.services.set(token.name, instance);
- }
-
- resolve<T>(target: new (...args: any[]) => T): T {
- const isInjectable = Reflect.getMetadata("injectable", target);
- if (!isInjectable) {
- throw new Error(`${target.name} 不是可注入的`);
- }
-
- const instance = new target();
-
- // 获取所有属性
- const properties = Object.getOwnPropertyNames(target.prototype);
-
- for (const property of properties) {
- const injectToken = Reflect.getMetadata("inject:token", target.prototype, property);
- if (injectToken) {
- const dependency = this.services.get(injectToken.name);
- if (!dependency) {
- throw new Error(`依赖 ${injectToken.name} 未注册`);
- }
- (instance as any)[property] = dependency;
- }
- }
-
- return instance;
- }
- }
- // 使用容器
- const container = new Container();
- container.register(DatabaseService, new DatabaseService());
- const userService = container.resolve(UserService);
- const users = userService.getUsers();
- console.log(users);
复制代码
最佳实践和注意事项
在使用装饰器时,有一些最佳实践和注意事项需要牢记:
1. 装饰器执行顺序
多个装饰器应用于同一声明时,它们的执行顺序如下:
• 对于类装饰器,从下到上执行
• 对于方法/属性/参数装饰器,从左到右执行
• 方法装饰器中,参数装饰器先于方法装饰器执行
示例:
- function classDecorator1(constructor: Function) {
- console.log("类装饰器1");
- }
- function classDecorator2(constructor: Function) {
- console.log("类装饰器2");
- }
- function methodDecorator1(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
- console.log("方法装饰器1");
- }
- function methodDecorator2(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
- console.log("方法装饰器2");
- }
- function paramDecorator1(target: Object, propertyKey: string, parameterIndex: number) {
- console.log("参数装饰器1");
- }
- function paramDecorator2(target: Object, propertyKey: string, parameterIndex: number) {
- console.log("参数装饰器2");
- }
- @classDecorator1
- @classDecorator2
- class ExampleClass {
- @methodDecorator1
- @methodDecorator2
- method(
- @paramDecorator1 param1: string,
- @paramDecorator2 param2: number
- ) {
- // 方法实现
- }
- }
- // 输出:
- // 参数装饰器2
- // 参数装饰器1
- // 方法装饰器2
- // 方法装饰器1
- // 类装饰器2
- // 类装饰器1
复制代码
2. 装饰器的性能考虑
装饰器可能会影响应用的性能,特别是在类实例化时。以下是一些优化建议:
• 避免在装饰器中执行重量级操作
• 考虑使用懒加载模式,只在需要时执行装饰器逻辑
• 对于频繁调用的方法,谨慎使用装饰器
示例:
- // 不好的做法 - 每次实例化时都会执行重量级操作
- function heavyDecorator(constructor: Function) {
- // 模拟重量级操作
- for (let i = 0; i < 1000000; i++) {
- Math.sqrt(i);
- }
- console.log("重量级装饰器执行完成");
- }
- @heavyDecorator
- class BadExample {
- // 类定义
- }
- // 好的做法 - 使用懒加载
- function lazyHeavyDecorator(constructor: Function) {
- let initialized = false;
-
- return class extends constructor {
- constructor(...args: any[]) {
- super(...args);
-
- if (!initialized) {
- // 模拟重量级操作
- for (let i = 0; i < 1000000; i++) {
- Math.sqrt(i);
- }
- console.log("懒加载重量级装饰器执行完成");
- initialized = true;
- }
- }
- };
- }
- @lazyHeavyDecorator
- class GoodExample {
- // 类定义
- }
- console.log("创建第一个实例");
- const good1 = new GoodExample(); // 执行重量级操作
- console.log("创建第二个实例");
- const good2 = new GoodExample(); // 不执行重量级操作
复制代码
3. 装饰器的可测试性
装饰器可能会使代码更难测试,以下是一些提高可测试性的建议:
• 将装饰器逻辑与业务逻辑分离
• 使用依赖注入来提供装饰器所需的依赖
• 为装饰器编写单元测试
示例:
- // 不好的做法 - 装饰器与业务逻辑紧密耦合
- function logBad(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
- const originalMethod = descriptor.value;
-
- descriptor.value = function(...args: any[]) {
- console.log(`调用方法 ${propertyKey}`);
- return originalMethod.apply(this, args);
- };
-
- return descriptor;
- }
- class BadExample {
- @logBad
- doSomething() {
- // 业务逻辑
- }
- }
- // 好的做法 - 装饰器与业务逻辑分离
- interface Logger {
- log(message: string): void;
- }
- class ConsoleLogger implements Logger {
- log(message: string): void {
- console.log(message);
- }
- }
- function logGood(logger: Logger) {
- return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
- const originalMethod = descriptor.value;
-
- descriptor.value = function(...args: any[]) {
- logger.log(`调用方法 ${propertyKey}`);
- return originalMethod.apply(this, args);
- };
-
- return descriptor;
- };
- }
- class GoodExample {
- constructor(private logger: Logger) {}
-
- @logGood(new ConsoleLogger())
- doSomething() {
- // 业务逻辑
- }
- }
- // 测试装饰器
- class TestLogger implements Logger {
- messages: string[] = [];
-
- log(message: string): void {
- this.messages.push(message);
- }
- }
- describe("logGood装饰器", () => {
- it("应该记录方法调用", () => {
- const testLogger = new TestLogger();
-
- // 创建一个测试类并应用装饰器
- class TestClass {
- @logGood(testLogger)
- testMethod() {
- return "test result";
- }
- }
-
- const instance = new TestClass();
- const result = instance.testMethod();
-
- expect(result).toBe("test result");
- expect(testLogger.messages).toContain("调用方法 testMethod");
- });
- });
复制代码
4. 装饰器的错误处理
装饰器中的错误可能会导致应用崩溃,以下是一些错误处理的建议:
• 在装饰器中添加适当的错误处理
• 考虑使用装饰器工厂来提供配置选项
• 记录装饰器中的错误以便调试
示例:
- function safeMethodDecorator(
- target: Object,
- propertyKey: string,
- descriptor: PropertyDescriptor
- ) {
- const originalMethod = descriptor.value;
-
- descriptor.value = function(...args: any[]) {
- try {
- return originalMethod.apply(this, args);
- } catch (error) {
- console.error(`方法 ${propertyKey} 执行出错:`, error);
- // 可以选择重新抛出错误或返回默认值
- throw error;
- }
- };
-
- return descriptor;
- }
- function configurableMethodDecorator(options: { logErrors: boolean, defaultValue?: any }) {
- return function (
- target: Object,
- propertyKey: string,
- descriptor: PropertyDescriptor
- ) {
- const originalMethod = descriptor.value;
-
- descriptor.value = function(...args: any[]) {
- try {
- return originalMethod.apply(this, args);
- } catch (error) {
- if (options.logErrors) {
- console.error(`方法 ${propertyKey} 执行出错:`, error);
- }
-
- if (options.defaultValue !== undefined) {
- return options.defaultValue;
- }
-
- throw error;
- }
- };
-
- return descriptor;
- };
- }
- class Example {
- @safeMethodDecorator
- method1() {
- throw new Error("测试错误");
- }
-
- @configurableMethodDecorator({ logErrors: true, defaultValue: "默认值" })
- method2() {
- throw new Error("测试错误");
- }
- }
- const example = new Example();
- try {
- example.method1();
- } catch (error) {
- console.log("捕获到错误:", error.message);
- }
- console.log(example.method2()); // 输出: 默认值
复制代码
5. 装饰器的类型安全
TypeScript的装饰器是实验性特性,类型支持有限。以下是一些提高类型安全的建议:
• 使用泛型来增强装饰器的类型安全性
• 为装饰器参数定义明确的类型
• 使用类型断言和类型守卫来处理类型不确定性
示例:
- // 不好的做法 - 缺乏类型安全
- function badDecorator(target: any, key: any, descriptor: any) {
- // 装饰器逻辑
- }
- // 好的做法 - 使用泛型和明确的类型
- function goodDecorator<T>(
- target: T,
- propertyKey: keyof T,
- descriptor: TypedPropertyDescriptor<(...args: any[]) => any>
- ) {
- // 装饰器逻辑
- }
- // 更好的做法 - 为装饰器参数定义类型
- interface ValidationOptions {
- required?: boolean;
- minLength?: number;
- pattern?: RegExp;
- }
- function validateProperty(options: ValidationOptions) {
- return function <T extends Record<string, any>>(
- target: T,
- propertyKey: keyof T
- ) {
- let value: any;
-
- const getter = function() {
- return value;
- };
-
- const setter = function(newVal: any) {
- if (options.required && (newVal === undefined || newVal === null)) {
- throw new Error(`${String(propertyKey)} 是必需的`);
- }
-
- if (options.minLength && typeof newVal === 'string' && newVal.length < options.minLength) {
- throw new Error(`${String(propertyKey)} 的最小长度是 ${options.minLength}`);
- }
-
- if (options.pattern && typeof newVal === 'string' && !options.pattern.test(newVal)) {
- throw new Error(`${String(propertyKey)} 不符合要求的格式`);
- }
-
- value = newVal;
- };
-
- Object.defineProperty(target, propertyKey, {
- get: getter,
- set: setter,
- enumerable: true,
- configurable: true
- });
- };
- }
- class User {
- @validateProperty({ required: true, minLength: 3 })
- name!: string;
-
- @validateProperty({ required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ })
- email!: string;
- }
- const user = new User();
- user.name = "Alice"; // 正确
- user.email = "alice@example.com"; // 正确
- try {
- user.name = "A"; // 抛出错误: name 的最小长度是 3
- } catch (error) {
- console.log("捕获到错误:", error.message);
- }
- try {
- user.email = "invalid-email"; // 抛出错误: email 不符合要求的格式
- } catch (error) {
- console.log("捕获到错误:", error.message);
- }
复制代码
总结
TypeScript装饰器是一种强大的特性,它允许我们在不修改原有代码的情况下,动态地扩展类的功能。通过装饰器,我们可以实现代码的复用和关注点分离,使代码更加清晰、可维护。
在本指南中,我们详细介绍了TypeScript装饰器的各个方面:
1. 装饰器基础:了解了装饰器的基本概念和语法
2. 类装饰器:学习了如何使用装饰器扩展或修改类的行为
3. 方法装饰器:探索了如何监视、修改或替换方法定义
4. 属性装饰器:掌握了如何监视或修改属性的定义
5. 参数装饰器:了解了如何监视方法的参数
6. 装饰器工厂:学习了如何创建可配置的装饰器
7. 实际应用场景:探讨了装饰器在日志记录、权限控制、性能监控、缓存和防抖节流等方面的应用
8. 装饰器元数据:了解了如何使用反射和元数据来增强装饰器的功能
9. 最佳实践和注意事项:学习了如何有效地使用装饰器,避免常见的陷阱
通过掌握TypeScript装饰器,你可以编写更加优雅、可维护和可扩展的代码。装饰器特别适用于框架开发、AOP(面向切面编程)和依赖注入等场景。希望本指南能够帮助你充分利用TypeScript装饰器的强大特性,让你的代码焕然一新。 |
|