自定义装饰器

自定义装饰器

定义自定义装饰器:

bash
1nest generate decorator aaa --flat --no-spec

生成代码:

ts
1import { SetMetadata } from '@nestjs/common'
2
3export const Aaa = (...args: string[]) => SetMetadata('aaa', args)

生成的代码中,有 SetMetadata 方法。之前我们使用它是这样用的

ts
1@SetMetadata('aaa','bbb')

可见装饰器就是一个函数,nest 会扫描并执行它。

我们自定义的 Aaa 装饰器包装了一层 setMetadata,想来也是同样的用法:

ts
1@Aaa('123')

测试一下,建立一个 guard,并且使用它:

typescript
1  @Get()
2  @Aaa('123')
3  @UseGuards(AaaGuard)
4  getHello(): string {
5    return this.appService.getHello();
6  }
typescript
1import { 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。

image-20231010201941371

组合装饰器

现在我们有三个装饰器:

typescript
1  @Get()
2  @Aaa('123')
3  @UseGuards(AaaGuard)

能否自定义一个装饰器,然后把三个装饰器组合到一起?

答案是可以的,不过需要用到 nest 提供的applyDecorators方法调用其他装饰器:

typescript
1// 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}

然后这样使用:

diff
1import { 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}

取参装饰器

使用内置的参数装饰器,可以用以下方式取参:

typescript
1  @Bbb()
2  getHello(@Query('id') id: string): string {
3    return this.appService.getHello() + id;
4  }

image-20231010220925486

我们也可以自己写这样的装饰器用以取参,nest 给我们提供了createParamDecorator方法:

typescript
1import { ExecutionContext, createParamDecorator } from '@nestjs/common'
2
3export const Ccc = createParamDecorator((data: string, ctx: ExecutionContext) => {
4  return ctx.switchToHttp().getRequest().query[data]
5})

createParamDecorator方法的 ctx 参数,提供了上下文,我们通过它可以获取到上下文内容信息。

使用这个装饰器:

typescript
1  @Bbb()
2  getHello(@Ccc('id') id: string): string {
3    return this.appService.getHello() + id;
4  }

该装饰器获取到的上下文信息。

image-20231010221850105

这样的话实现一个@Query 装饰器也不是大问题:

typescript
1import { 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})

image-20231010222741560

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

image-20231010223409655

Class 装饰器

像 @Controller 这样用来装饰 class 的装饰器,同样也可以组合自定义装饰器来使用。

方法一模一样,这里就不展开了。

总结

  1. 自定义装饰器就是一个函数,nest 会扫描并执行它。这里的例子是封装 @SetMetadata 装饰器。
  2. 组合多个装饰器需要用到 applyDecorators API。
  3. 取参装饰器需要用到 createParamDecorator API。它能拿到 ExecutionContext,进而拿到 reqeust、response 等上下文信息,可以实现很多内置装饰器的功能。