学习目标
- 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
}
}
类比一下:
| 装饰器类型 | 作用 | 比喻 | |-----------|------|------| | 类装饰器 | 给整个类加功能 | 给房子装修 | | 方法装饰器 | 给方法加功能 | 给门装防盗锁 | | 属性装饰器 | 给属性加功能 | 给窗户装窗帘 |
方法装饰器实战:自动日志记录
装饰器工厂:带参数的装饰器
有时候你想让装饰器更灵活——比如不同的方法用不同的日志级别。
装饰器工厂就是一个返回装饰器的函数:
// 装饰器工厂
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) { ... }
}
类比一下:
普通装饰器就像买现成的手机壳。 装饰饰器工厂就像定制手机壳——你可以选颜色、图案、材质,工厂根据你的要求生产。
装饰器工厂实战:权限验证
类装饰器:给整个类加功能
常见错误
常见错误
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等。这些工具让你可以轻松地从现有类型派生出新类型,就像变魔术一样。
就像学会了武功招式,现在要学内功心法了!
交互式练习
装饰器练习:自动日志
创建一个方法装饰器,自动记录方法调用的日志
章节测验
启用装饰器需要在tsconfig.json中设置什么?