NestJs 自定義裝飾器

2023-09-08 11:36 更新

Nest 是基于裝飾器這種語言特性而創(chuàng)建的。在很多常見的編程語言中,裝飾器是一個(gè)廣為人知的概念,但在 JavaScript 世界中,這個(gè)概念仍然相對(duì)較新。所以為了更好地理解裝飾器是如何工作的,你應(yīng)該看看 這篇 文章。下面給出一個(gè)簡(jiǎn)單的定義:

ES2016 裝飾器是一個(gè)表達(dá)式,它返回一個(gè)可以將目標(biāo)、名稱和屬性描述符作為參數(shù)的函數(shù)。通過在裝飾器前面添加一個(gè) @ 字符并將其放置在你要裝飾的內(nèi)容的最頂部來應(yīng)用它。可以為類、方法或?qū)傩远x裝飾器。

參數(shù)裝飾器

Nest 提供了一組非常實(shí)用的參數(shù)裝飾器,可以結(jié)合 HTTP 路由處理器(route handlers)一起使用。下面的列表展示了Nest 裝飾器和原生 Express(或 Fastify)中相應(yīng)對(duì)象的映射。

@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

另外,你還可以創(chuàng)建自定義裝飾器。這非常有用。

在 Node.js 中,會(huì)經(jīng)常將需要傳遞的值加到請(qǐng)求對(duì)象的屬性中。然后在每個(gè)路由處理程序中手動(dòng)提取它們,使用如下代碼:

const user = req.user;

為了使代碼更具可讀性和透明性,我們可以創(chuàng)建一個(gè) @User() 裝飾器并在所有控制器中使用它。

user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const User = createParamDecorator((data: unknown, ctx: ExecutionContext) => {
  const request = ctx.switchToHttp().getRequest();
  return request.user;
});

現(xiàn)在你可以在任何你想要的地方很方便地使用它。

@Get()
async findOne(@User() user: UserEntity) {
  console.log(user);
}

傳遞數(shù)據(jù)

當(dāng)裝飾器的行為取決于某些條件時(shí),可以使用 data 參數(shù)將參數(shù)傳遞給裝飾器的工廠函數(shù)。 一個(gè)用例是自定義裝飾器,它通過鍵從請(qǐng)求對(duì)象中提取屬性。 例如,假設(shè)我們的身份驗(yàn)證層驗(yàn)證請(qǐng)求并將用戶實(shí)體附加到請(qǐng)求對(duì)象。 經(jīng)過身份驗(yàn)證的請(qǐng)求的用戶實(shí)體可能類似于:

{
  "id": 101,
  "firstName": "Alan",
  "lastName": "Turing",
  "email": "alan@email.com",
  "roles": ["admin"]
}

讓我們定義一個(gè)將屬性名作為鍵的裝飾器,如果存在則返回關(guān)聯(lián)的值(如果不存在或者尚未創(chuàng)建 user 對(duì)象,則返回 undefined)。

user.decorator.ts
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 && user[data] : user;
});

然后,您可以通過控制器中的 @User() 裝飾器訪問以下特定屬性:

@Get()
async findOne(@User('firstName') firstName: string) {
  console.log(`Hello ${firstName}`);
}

您可以使用具有不同鍵的相同裝飾器來訪問不同的屬性。如果用戶對(duì)象復(fù)雜,使用此方法可以使請(qǐng)求處理程序編寫更容易、并且可讀性更高。

對(duì)于 TypeScript 用戶,請(qǐng)注意這 createParamDecorator() 是通用的。這意味著您可以顯式實(shí)施類型安全性,例如 createParamDecorator((data, ctx) => ...)或者,在工廠函數(shù)中指定參數(shù)類型,例如createParamDecorator((data: string, ctx) => ...) 。如果省略這兩個(gè), 參數(shù) data 的類型為 any。

使用管道

Nest 對(duì)待自定義的路由參數(shù)裝飾器和自身內(nèi)置的裝飾器(@Body(),@Param() 和 @Query())一樣。這意味著管道也會(huì)因?yàn)樽远x注釋參數(shù)(在本例中為 user 參數(shù))而被執(zhí)行。此外,你還可以直接將管道應(yīng)用到自定義裝飾器上:

@Get()
async findOne(@User(new ValidationPipe()) user: UserEntity) {
  console.log(user);
}
請(qǐng)注意,validateCustomDecorators 選項(xiàng)必須設(shè)置為 true。默認(rèn)情況下,ValidationPipe 不驗(yàn)證使用自定義裝飾器注釋的參數(shù)。

裝飾器聚合

Nest 提供了一種輔助方法來聚合多個(gè)裝飾器。例如,假設(shè)您要將與身份驗(yàn)證相關(guān)的所有裝飾器聚合到一個(gè)裝飾器中。這可以通過以下方法實(shí)現(xiàn):

import { applyDecorators } from '@nestjs/common';

export function Auth(...roles: Role[]) {
  return applyDecorators(
    SetMetadata('roles', roles),
    UseGuards(AuthGuard, RolesGuard),
    ApiBearerAuth(),
    ApiUnauthorizedResponse({ description: 'Unauthorized"' })
  );
}

然后,你可以參照以下方式使用 @Auth() 自定義裝飾器:

@Get('users')
@Auth('admin')
findAllUsers() {}

這具有通過一個(gè)聲明應(yīng)用所有四個(gè)裝飾器的效果。

來自 @nestjs/swagger 依賴中的 @ApiHideProperty() 裝飾器無法聚合,因此此裝飾器無法正常使用 applyDecorators 方法。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)