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裝飾器。
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);
}
當(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 方法。
更多建議: