背景
Nest是围绕一种称为装饰器的语言特性构建的。
装饰器风格的实现
Nest在面向对象设计中用到了装饰器模式去组织代码。
装饰器模式是一种动态地往一个类别中添加新的行为的设计模式
在写项目的时候,可以说处处都是围绕着装饰器模式去进行定义的。
例如将一个类定义为一个controller
,并将方法暴露为Get请求的方法是像下面这样处理:
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
而能使用装饰器,其实还是因为js提供了支持。具体细节可以看看Typescript的装饰器文档。
装饰器是JavaScript 的第 2 阶段提案,可作为 TypeScript 的实验性功能使用
装饰器提供了一种为类声明和成员添加注释和元编程语法
的方法。因此,很多装饰器,其实就是应用reflect-metadata包,去定义各种元数据。
Reflect Metadata 是 ES7 的一个提案,它主要用来在声明的时候添加和读取元数据。
因为这些js提供的语言特性,我们在使用时,就需要在tsconfig.json中配置compileOptions的参数:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
大多数场景下,装饰器其实都是在对元数据进行定义或者操作。例如,下面是Nest中对Http请示装饰器的逻辑代码:
const defaultMetadata = {
[PATH_METADATA]: '/',
[METHOD_METADATA]: RequestMethod.GET,
};
export const RequestMapping = (
metadata: RequestMappingMetadata = defaultMetadata,
): MethodDecorator => {
const pathMetadata = metadata[PATH_METADATA];
const path = pathMetadata && pathMetadata.length ? pathMetadata : '/';
const requestMethod = metadata[METHOD_METADATA] || RequestMethod.GET;
return (
target: object,
key: string | symbol,
descriptor: TypedPropertyDescriptor<any>,
) => {
Reflect.defineMetadata(PATH_METADATA, path, descriptor.value); // 注意这里! 元数据定义
Reflect.defineMetadata(METHOD_METADATA, requestMethod, descriptor.value); // 注意这里! 元数据定义
return descriptor;
};
};
之前刚了解js中使用装饰器风格的时候,自己也写过一个简答的用装饰器风格封装koa的项目:deco-brick,可以结合代码理解下这里所说的。
Typescript中的decorator
Typescript提供了五种decorator:
- Class Decorators(类装饰器)
- Method Decorators(类方法装饰器)
- Parameter Decorators(参数装饰器)
- Accessor Decorators(访问器装饰器)
- Property Decorators(类属性装饰器)
具体还是参考Typescript Decorator文档会讲得比较清楚
但是在Nest中,只用到Class Decorators(类装饰器)、Method Decorators(类方法装饰器)、Parameter Decorators(参数装饰器)三种
export declare function applyDecorators(...decorators: Array<ClassDecorator | MethodDecorator | PropertyDecorator>): <TFunction extends Function, Y>(target: object | TFunction, propertyKey?: string | symbol, descriptor?: TypedPropertyDescriptor<Y>) => void;
使用Nest提供的关于decorator的方法
创建参数装饰器——createParamDecorator
createParamDecorator
是创建一个参数装饰器,提供这个方法主要的原因是我们在开发过程中,需要针对执行上下文去做一下操作,这个方法会将执行上下文传递到装饰器中。
如下:
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest(); // 将执行上下文转换成express的request
return request.user;
},
);
组合装饰器——applyDecorators
为了逻辑代码的切分和管理,会在开发中写了多个装饰器,Nest则提供了方法去组合多个装饰器,以达到重新排练组合去复用装饰器。
import { applyDecorators } from '@nestjs/common';
export function Auth(...roles: Role[]) {
return applyDecorators(
SetMetadata('roles', roles),
UseGuards(AuthGuard, RolesGuard),
ApiBearerAuth(),
ApiUnauthorizedResponse({ description: 'Unauthorized' }),
);
}
自定义MetaData——SetMetadata和Reflector
Nest也提供了自己去设置和获取MetaData的方法暴露出来,个人用得比较多的就是在自己编写模块的使用会用到。
下面代码是一个方法装饰器的例子
import { SetMetadata } from '@nestjs/common';
// 为一个方法设置一个监听事件名
export const OnProcessEvent = (
eventName: string,
eventStatus: ProcessEvent,
): MethodDecorator => SetMetadata(MY_PROCESS_EVENT, { eventName, eventStatus });
而获取设置得MetaData,怎是使用@nestjs/core
中的Reflector
import { Reflector } from '@nestjs/core';
// 根据对象的方法获取配置的监听事件名
getEventMetadata(target: Type<any> | Function): {
eventName: string;
eventStatus: ProcessEvent;
} {
return this.reflector.get(MY_PROCESS_EVENT, target);
}
这里的例子可以参考我的示例代码