自定义装饰器
自定义装饰器
Nest 是围绕一种称为装饰器的语言特性构建的。装饰器在许多常用编程语言中是一个众所周知的概念,但在 JavaScript 世界中,它们仍然相对较新。为了更好地理解装饰器的工作原理,我们建议阅读这篇文章。这里有一个简单的定义:
ES2016 装饰器是一个表达式,它返回一个函数,可以接受目标、名称和属性描述符作为参数。
你通过在装饰器前加上 @ 字符并将其放在你要装饰的内容的最顶部来应用它。装饰器可以为类、方法或属性定义。
参数装饰器
Nest 提供了一组有用的参数装饰器,你可以与 HTTP 路由处理程序一起使用。下面是提供的装饰器列表以及它们代表的普通 Express(或 Fastify)对象
| 装饰器 | 对应的平台对象 |
|---|---|
@Request(), @Req() | req |
@Response(), @Res() | res |
@Next() | next |
@Session() | req.session |
@Param(param?: string) | req.params / req.params[param] |
@Body(param?: string) | req.body / req.body[param] |
@Query(param?: string) | req.query / req.query[param] |
@Headers(param?: string) | req.headers / req.headers[param] |
@Ip() | req.ip |
@HostParam() | req.hosts |
此外,你可以创建自己的自定义装饰器。为什么这很有用?
在 node.js 世界中,将属性附加到 request 对象是常见做法。然后你在每个路由处理程序中手动提取它们,使用如下代码:
const user = req.user;
为了使你的代码更具可读性和透明度,你可以创建一个 @User() 装饰器并在所有控制器中重复使用它。
@@filename(user.decorator)
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
然后,你可以在任何符合要求的地方简单地使用它。
@@filename()
@Get()
async findOne(@User() user: UserEntity) {
console.log(user);
}
@@switch
@Get()
@Bind(User())
async findOne(user) {
console.log(user);
}
传递数据
当你的装饰器的行为取决于某些条件时,你可以使用 data 参数向装饰器的工厂函数传递参数。一个用例是自定义装饰器,它通过键从请求对象中提取属性。例如,假设我们的身份验证层验证请求并将用户实体附加到请求对象。经过身份验证的请求的用户实体可能如下所示:
{
"id": 101,
"firstName": "Alan",
"lastName": "Turing",
"email": "alan@email.com",
"roles": ["admin"]
}
让我们定义一个装饰器,它接受属性名作为键,如果存在则返回关联的值(如果不存在或 user 对象尚未创建,则返回 undefined)。
@@filename(user.decorator)
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user?.[data] : user;
},
);
@@switch
import { createParamDecorator } from '@nestjs/common';
export const User = createParamDecorator((data, ctx) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user && user[data] : user;
});
以下是你如何在控制器中通过 @User() 装饰器访问特定属性:
@@filename()
@Get()
async findOne(@User('firstName') firstName: string) {
console.log(`Hello ${firstName}`);
}
@@switch
@Get()
@Bind(User('firstName'))
async findOne(firstName) {
console.log(`Hello ${firstName}`);
}
你可以使用相同的装饰器与不同的键来访问不同的属性。如果 user 对象很深或很复杂,这可以使请求处理程序实现更容易、更可读。
info 提示 对于 TypeScript 用户,请注意
createParamDecorator<T>()是一个泛型。这意味着你可以显式强制类型安全,例如createParamDecorator<string>((data, ctx) => ...)。或者,在工厂函数中指定参数类型,例如createParamDecorator((data: string, ctx) => ...)。如果你省略两者,data的类型将是any。
与管道一起工作
Nest 以与内置装饰器(@Body()、@Param() 和 @Query())相同的方式处理自定义参数装饰器。这意味着管道也会为自定义注释的参数执行(在我们的示例中是 user 参数)。此外,你可以直接将管道应用于自定义装饰器:
@@filename()
@Get()
async findOne(
@User(new ValidationPipe({ validateCustomDecorators: true }))
user: UserEntity,
) {
console.log(user);
}
@@switch
@Get()
@Bind(User(new ValidationPipe({ validateCustomDecorators: true })))
async findOne(user) {
console.log(user);
}
info 提示 请注意,
validateCustomDecorators选项必须设置为 true。默认情况下,ValidationPipe不会验证使用自定义装饰器注释的参数。
装饰器组合
Nest 提供了一个辅助方法来组合多个装饰器。例如,假设你想要将所有与身份验证相关的装饰器组合成一个装饰器。这可以通过以下构造来完成:
@@filename(auth.decorator)
import { applyDecorators } from '@nestjs/common';
export function Auth(...roles: Role[]) {
return applyDecorators(
SetMetadata('roles', roles),
UseGuards(AuthGuard, RolesGuard),
ApiBearerAuth(),
ApiUnauthorizedResponse({ description: 'Unauthorized' }),
);
}
@@switch
import { applyDecorators } from '@nestjs/common';
export function Auth(...roles) {
return applyDecorators(
SetMetadata('roles', roles),
UseGuards(AuthGuard, RolesGuard),
ApiBearerAuth(),
ApiUnauthorizedResponse({ description: 'Unauthorized' }),
);
}
然后你可以如下使用这个自定义的 @Auth() 装饰器:
@Get('users')
@Auth('admin')
findAllUsers() {}
这具有通过单个声明应用所有四个装饰器的效果。
warning 警告 来自
@nestjs/swagger包的@ApiHideProperty()装饰器不可组合,无法与applyDecorators函数正常工作。