自定义装饰器
自定义装饰器
定义自定义装饰器:
bash1nest generate decorator aaa --flat --no-spec
生成代码:
ts1import { SetMetadata } from '@nestjs/common' 2 3export const Aaa = (...args: string[]) => SetMetadata('aaa', args)
生成的代码中,有 SetMetadata 方法。之前我们使用它是这样用的
ts1@SetMetadata('aaa','bbb')
可见装饰器就是一个函数,nest 会扫描并执行它。
我们自定义的 Aaa 装饰器包装了一层 setMetadata,想来也是同样的用法:
ts1@Aaa('123')
测试一下,建立一个 guard,并且使用它:
typescript1 @Get() 2 @Aaa('123') 3 @UseGuards(AaaGuard) 4 getHello(): string { 5 return this.appService.getHello(); 6 }
typescript1import { CanActivate, ExecutionContext, Inject, Injectable } from '@nestjs/common' 2import { Reflector } from '@nestjs/core' 3import { Observable } from 'rxjs' 4 5@Injectable() 6export class AaaGuard implements CanActivate { 7 @Inject(Reflector) 8 private readonly reflector: Reflector 9 canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> { 10 const a = context.getClass() 11 const b = context.getHandler() 12 const meta = this.reflector.get('aaa', context.getHandler()) 13 return true 14 } 15}
通过断点可以看到,跟 SetMetadata 一样都可以通过 reflector 获取到 class、handler 和 metadata。

组合装饰器
现在我们有三个装饰器:
typescript1 @Get() 2 @Aaa('123') 3 @UseGuards(AaaGuard)
能否自定义一个装饰器,然后把三个装饰器组合到一起?
答案是可以的,不过需要用到 nest 提供的applyDecorators方法调用其他装饰器:
typescript1// bbb.guard 2import { Get, UseGuards, applyDecorators } from '@nestjs/common' 3import { Aaa } from 'src/aaa.decorator' 4import { AaaGuard } from 'src/aaa.guard' 5 6export const Bbb = () => { 7 return applyDecorators(Get(), UseGuards(AaaGuard), Aaa('123')) 8}
然后这样使用:
diff1import { Controller } from '@nestjs/common'; 2import { AppService } from './app.service'; 3import { Bbb } from './bbb/bbb.decorator'; 4 5@Controller() 6export class AppController { 7 constructor(private readonly appService: AppService) {} 8 9- @Get() 10- @Aaa('123') 11- @UseGuards(AaaGuard) 12+ @Bbb() 13 getHello(): string { 14 return this.appService.getHello(); 15 } 16}
取参装饰器
使用内置的参数装饰器,可以用以下方式取参:
typescript1 @Bbb() 2 getHello(@Query('id') id: string): string { 3 return this.appService.getHello() + id; 4 }

我们也可以自己写这样的装饰器用以取参,nest 给我们提供了createParamDecorator方法:
typescript1import { ExecutionContext, createParamDecorator } from '@nestjs/common' 2 3export const Ccc = createParamDecorator((data: string, ctx: ExecutionContext) => { 4 return ctx.switchToHttp().getRequest().query[data] 5})
createParamDecorator方法的 ctx 参数,提供了上下文,我们通过它可以获取到上下文内容信息。
使用这个装饰器:
typescript1 @Bbb() 2 getHello(@Ccc('id') id: string): string { 3 return this.appService.getHello() + id; 4 }
该装饰器获取到的上下文信息。

这样的话实现一个@Query 装饰器也不是大问题:
typescript1import { ExecutionContext, createParamDecorator } from '@nestjs/common' 2 3export const MyQuery = createParamDecorator((data: string, ctx: ExecutionContext) => { 4 const req = ctx.switchToHttp().getRequest() 5 return data ? req.query[data] : req.query 6})

这些自定义的参数装饰器也支持 Pipe 修改参数:

Class 装饰器
像 @Controller 这样用来装饰 class 的装饰器,同样也可以组合自定义装饰器来使用。
方法一模一样,这里就不展开了。
总结
- 自定义装饰器就是一个函数,nest 会扫描并执行它。这里的例子是封装 @SetMetadata 装饰器。
- 组合多个装饰器需要用到 applyDecorators API。
- 取参装饰器需要用到 createParamDecorator API。它能拿到 ExecutionContext,进而拿到 reqeust、response 等上下文信息,可以实现很多内置装饰器的功能。