什么是装饰器?
装饰器(Decorator)是一种特殊类型的声明,它能够被附加到类声明、方法、属性或参数上。装饰器使用 @expression 这样的形式,其中 expression 必须是一个函数,它会在运行时被调用。
装饰器的基本语法
typescript
// 类装饰器
@Controller('users')
class UserController {}
// 方法装饰器
@Get('profile')
getProfile() {}
// 参数装饰器
getUser(@Param('id') id: string) {}
// 属性装饰器
@Inject()
private userService: UserService;为什么要使用装饰器?
让我们通过一个具体的例子来理解装饰器的价值。假设我们要开发一个用户管理API:
1. 传统Express写法
javascript
const express = require('express');
const app = express();
// 中间件定义
const validateUser = (req, res, next) => {
if (!req.body.name) {
return res.status(400).json({ error: '用户名是必须的' });
}
next();
};
const logRequest = (req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
};
// 路由定义
app.post('/users',
logRequest,
validateUser,
async (req, res) => {
try {
const user = await createUser(req.body);
res.json(user);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
);2. NestJS装饰器写法
typescript
@Controller('users')
export class UserController {
constructor(private userService: UserService) {}
@Post()
@UsePipes(ValidationPipe)
@UseInterceptors(LoggingInterceptor)
async createUser(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
}
// DTO定义
class CreateUserDto {
@IsNotEmpty({ message: '用户名是必须的' })
name: string;
}常用装饰器详解
1. 控制器相关装饰器
typescript
@Controller('users') // 定义控制器基础路径
export class UserController {
@Get() // GET /users
@Post() // POST /users
@Put(':id') // PUT /users/:id
@Delete(':id') // DELETE /users/:id
@Patch(':id') // PATCH /users/:id
}2. 请求参数装饰器
typescript
@Get(':id')
findOne(
@Param('id') id: string, // 路由参数
@Query('type') type: string, // 查询参数 ?type=xxx
@Body() createUserDto: CreateUserDto, // 请求体
@Headers('authorization') token: string, // 请求头
) {
// 处理逻辑
}3. 守卫和拦截器装饰器
typescript
@UseGuards(AuthGuard) // 身份验证守卫
@UseInterceptors(LoggingInterceptor) // 日志拦截器
@UsePipes(ValidationPipe) // 验证管道
async updateUser(@Body() updateUserDto: UpdateUserDto) {
// 处理逻辑
}4. 自定义装饰器
typescript
// 创建自定义装饰器
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
// 使用自定义装饰器
@Get('profile')
getProfile(@User() user: UserEntity) {
return user;
}装饰器工作原理
装饰器本质上是一个函数,它可以通过装饰器工厂函数来进行参数传递。让我们看一个简单的例子:
typescript
// 装饰器工厂函数
function Logger(prefix: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// 保存原始方法
const originalMethod = descriptor.value;
// 修改方法的行为
descriptor.value = function (...args: any[]) {
console.log(`${prefix} 调用方法 ${propertyKey}`);
const result = originalMethod.apply(this, args);
console.log(`${prefix} 方法返回:`, result);
return result;
};
};
}
// 使用装饰器
class Example {
@Logger('DEBUG')
sayHello(name: string) {
return `Hello, ${name}!`;
}
}装饰器的执行顺序
当多个装饰器应用于一个声明上时,它们的执行顺序如下:
- 装饰器表达式按从上到下的顺序求值
- 求值的结果会按从下到上的顺序执行
typescript
@Controller('users')
@UseGuards(AuthGuard)
@UseInterceptors(LoggingInterceptor)
export class UserController {
// 执行顺序:
// 1. Controller装饰器
// 2. UseInterceptors装饰器
// 3. UseGuards装饰器
}装饰器的实际应用场景
1. 权限控制
typescript
@Roles('admin') // 角色检查装饰器
@Get('sensitive-data')
getSensitiveData() {
return this.dataService.getSensitiveData();
}2. 性能监控
typescript
@Monitor() // 监控方法执行时间
@Get('heavy-operation')
async heavyOperation() {
// 耗时操作
}3. 缓存处理
typescript
@CacheKey('users')
@CacheTTL(30) // 缓存30秒
@Get()
async findAll() {
return this.userService.findAll();
}最佳实践
- 保持装饰器单一职责:每个装饰器应该只做一件事情
- 合理使用组合:多个装饰器可以组合使用,但要注意执行顺序
- 注意性能影响:装饰器会在运行时执行,过多的装饰器可能影响性能
- 做好错误处理:在装饰器中要处理好异常情况
总结
装饰器是NestJS框架中非常重要的特性,它们:
- 提供了声明式的编程方式
- 使代码更加简洁和可维护
- 支持面向切面编程(AOP)
- 让功能扩展变得更加灵活
通过合理使用装饰器,我们可以写出更加优雅、可维护的代码。但同时也要注意不要过度使用装饰器,以免影响代码的可读性和性能。