Skip to content

什么是装饰器? ​

装饰器(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}!`;
  }
}

装饰器的执行顺序 ​

当多个装饰器应用于一个声明上时,它们的执行顺序如下:

  1. 装饰器表达式按从上到下的顺序求值
  2. 求值的结果会按从下到上的顺序执行
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();
}

最佳实践 ​

  1. 保持装饰器单一职责:每个装饰器应该只做一件事情
  2. 合理使用组合:多个装饰器可以组合使用,但要注意执行顺序
  3. 注意性能影响:装饰器会在运行时执行,过多的装饰器可能影响性能
  4. 做好错误处理:在装饰器中要处理好异常情况

总结 ​

装饰器是NestJS框架中非常重要的特性,它们:

  1. 提供了声明式的编程方式
  2. 使代码更加简洁和可维护
  3. 支持面向切面编程(AOP)
  4. 让功能扩展变得更加灵活

通过合理使用装饰器,我们可以写出更加优雅、可维护的代码。但同时也要注意不要过度使用装饰器,以免影响代码的可读性和性能。