首页/11

11章:装饰器:优雅地扩展功能

用装饰器给类和方法穿上外套——日志、验证、权限一步到位

40分钟
4个学习目标

学习目标

  • 1理解装饰器是什么,为什么需要它
  • 2学会使用类装饰器
  • 3掌握方法装饰器的实际应用
  • 4了解属性装饰器

上一章回顾:异步搞定了,但代码越来越重复

在上一章中,我们学会了用 async/await 处理异步操作。博客现在可以从服务器获取数据、保存文章了。

但你可能发现了新问题:

你的博客有好几个服务类,每个方法都需要:

  • 记录日志(调用了什么方法,传了什么参数)
  • 处理错误(统一的错误处理逻辑)
  • 性能监控(方法执行了多长时间)
class PostService {
  async getPost(id: number) {
    console.log('调用 getPost', id);  // 重复代码
    const start = Date.now();          // 重复代码
    try {
      const post = await fetchPost(id);
      console.log('getPost 耗时:', Date.now() - start); // 重复代码
      return post;
    } catch (error) {
      console.error('getPost 错误:', error); // 重复代码
      throw error;
    }
  }

  async createPost(data: PostData) {
    console.log('调用 createPost', data);  // 又重复一遍
    const start = Date.now();              // 又重复一遍
    // ... 同样的错误处理
  }
}

每个方法都要写一遍同样的代码,太烦了!

装饰器就是解决这个问题的方案。

类比一下:

装饰器就像给手机"装壳"——手机本身功能不变,但外壳可以增加保护、支架、卡包等功能。你不用修改手机内部结构,只需要套上不同的壳。

什么是装饰器?

装饰器是一个函数,它可以"装饰"(扩展)类、方法、属性的功能,而不需要修改原始代码。

装饰器的类型:

  • 类装饰器:装饰整个类
  • 方法装饰器:装饰类的方法
  • 属性装饰器:装饰类的属性
  • 参数装饰器:装饰方法的参数

启用装饰器:

装饰器目前还是实验性特性,需要在 tsconfig.json 中启用:

{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}

类比一下:

| 装饰器类型 | 作用 | 比喻 | |-----------|------|------| | 类装饰器 | 给整个类加功能 | 给房子装修 | | 方法装饰器 | 给方法加功能 | 给门装防盗锁 | | 属性装饰器 | 给属性加功能 | 给窗户装窗帘 |

方法装饰器实战:自动日志记录

typescript
加载中...

装饰器工厂:带参数的装饰器

有时候你想让装饰器更灵活——比如不同的方法用不同的日志级别。

装饰器工厂就是一个返回装饰器的函数:

// 装饰器工厂
function LogWithLevel(level: 'info' | 'warn' | 'error') {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    // 这里可以用 level 参数
  };
}

// 使用
class PostService {
  @LogWithLevel('info')
  getPost(id: number) { ... }

  @LogWithLevel('error')
  deletePost(id: number) { ... }
}

类比一下:

普通装饰器就像买现成的手机壳。 装饰饰器工厂就像定制手机壳——你可以选颜色、图案、材质,工厂根据你的要求生产。

装饰器工厂实战:权限验证

typescript
加载中...

类装饰器:给整个类加功能

typescript
加载中...

常见错误

常见错误

1. 忘记启用 experimentalDecorators

// 报错:装饰器是实验性特性
// 解决:在 tsconfig.json 中添加
{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}

2. 装饰器执行顺序

// 装饰器从下往上执行
@First()
@Second()
class MyClass {}

// 执行顺序:
// 1. Second() 被求值
// 2. First() 被求值
// 3. Second 的装饰器函数被调用
// 4. First 的装饰器函数被调用

3. 方法装饰器中忘记处理返回值

// 错误:没有返回原始方法的结果
descriptor.value = function (...args: any[]) {
  console.log('调用方法');
  originalMethod.apply(this, args); // 没有 return!
};

// 正确:返回结果
descriptor.value = function (...args: any[]) {
  console.log('调用方法');
  return originalMethod.apply(this, args);
};

4. 装饰器中 this 指向问题

// 错误:直接调用会丢失 this
descriptor.value = (...args: any[]) => { // 箭头函数没有自己的 this
  return originalMethod(...args);
};

// 正确:使用普通函数
descriptor.value = function (...args: any[]) {
  return originalMethod.apply(this, args);
};

动手实践

现在轮到你了!让我们用装饰器来优化博客代码。

练习1:性能监控装饰器

创建一个 @Measure 装饰器:

  • 记录方法的执行时间
  • 如果超过阈值,输出警告

练习2:缓存装饰器

创建一个 @Cache(ttl) 装饰器:

  • 缓存方法的返回值
  • 在 ttl 时间内重复调用直接返回缓存

练习3:组合使用

为 PostService 的方法组合使用多个装饰器:

  • @Log 记录日志
  • @Measure 监控性能
  • @Authorize('user') 权限验证

本章总结

恭喜你!你已经学会了用装饰器优雅地扩展代码功能。

本章要点:

| 概念 | 作用 | 比喻 | |-----|------|------| | 装饰器 | 不修改原代码,添加新功能 | 给手机装壳 | | 装饰器工厂 | 带参数的装饰器 | 定制手机壳 | | 方法装饰器 | 装饰类的方法 | 给门装防盗锁 | | 类装饰器 | 装饰整个类 | 给房子装修 |

对比一下:

| 学习前 | 学习后 | |-------|-------| | 每个方法都写重复的日志代码 | @Log 装饰器一行搞定 | | 权限验证逻辑分散在各处 | @Authorize 统一管理 | | 代码重复、难以维护 | 代码简洁、职责清晰 |

下一章预告:

在下一章中,我们将学习TypeScript的实用工具类型——Partial、Pick、Omit、Record等。这些工具让你可以轻松地从现有类型派生出新类型,就像变魔术一样。

就像学会了武功招式,现在要学内功心法了!

交互式练习

1 / 1

装饰器练习:自动日志

创建一个方法装饰器,自动记录方法调用的日志

typescript
Loading...

章节测验

问题 1 / 3得分: 0 / 3

启用装饰器需要在tsconfig.json中设置什么?

A
emitDecoratorMetadata: true
B
experimentalDecorators: true
C
decorators: true
D
enableDecorators: true