从0~1手写Nest.js - (2)进一步完善控制器装饰器/参数装饰器/模块装饰器和对请求路由进行初步分析

145 阅读3分钟

一. 前言

第一篇文章我们实现了手动搭建出一个极简的Nest服务器,但这是结束也即是开端!

停下来想一下,我们现在既然已经有了最最重要的服务器,那么我们现在需要什么?

对了,当然是写接口,服务器服务器嘛,不就是玩数据的嘛

那么问题来了, 如果我目前想实现一个接口都需要什么步骤呢?

举个栗子

我目前想在浏览器中输入localhost:3000/helle/create 然后页面得到'hello nest'

因为Nestjs的底层就是封装的express,所以我们直接拿express来解决一下这个问题

如果用过express的同学可能会知道,express来定义路由与返回数据的方式如下:

const app = express()

app.get('/hello/create', (req, res, next => {
    res.send('hello nest')
}))

非常通俗易懂对吧,虽然但是,我们还是需要来拆解一下这段代码都干了些什么,因为这对我们后面写各种装饰器有莫大的帮助

const app = express() 
app.get('/hello/create', (req, res, next => { res.send('hello nest') }))

/** 
 *   1.app.get() 表示该请求方式为GET
 *   2.app.get('/hello/create') 请求路由
 *   3.(req, res, next => { res.send('hello nest') })  req: 请求参数, res: 返回体, next:中间件
*/ 

分析这段express之后,得到了3个关键的信息,请求方式 / 路由路径 / 请求参数与返回值

☆在Nest中,一个控制器(Controller)代表着一个单独的模块,可以通过控制器装饰器来设置统一的请求前缀

而且在Nest,它并没有用这种方式来定义一个请求,而是使用各种封装后的装饰器来实现,比如:

  • @Controller('/hello') => Nest中可以通过控制器装饰器来设置统一的路由前缀
  • @Get('/create') => 可以使用参数装饰器来配置该接口的路由路径

二. 完善模块装饰器 @Module

// 类装饰器(模块装饰器)

// 导入元数据包
import 'reflect-metadata'

interface ModuleMetadata {
  controllers: Function[]
}

/**
 * @param metadata 类的数组
 * @returns 类装饰器(模块装饰器)
 */
export const Module = (metadata: ModuleMetadata): ClassDecorator => {
  // 给模块类添加元数据, target是AppModule或其他模块类, 元数据的名字叫controllers, 值是controllers数组: controllers: [AppController]
  return (target: Function) => {
    console.log(target)
    Reflect.defineMetadata('controllers', metadata.controllers, target)
  }
}

三. 控制器装饰器 @Controller


// 控制器装饰器

// 导入元数据包
import 'reflect-metadata'

interface ControllerOptions {
  prefix?: string
}

// 控制器参数重载
export function Controller(): ClassDecorator // 传入空字符串
export function Controller(prefix: string): ClassDecorator // 传入字符串
export function Controller(options: ControllerOptions): ClassDecorator // 传入对象

export function Controller(prefixOrOptions?: string | ControllerOptions): ClassDecorator {
  let options: ControllerOptions = {}

  if (typeof prefixOrOptions === 'string') options.prefix = prefixOrOptions
  if (typeof prefixOrOptions === 'object') options = prefixOrOptions

  return (target: Function) => {
    // 给控制器类添加prefix这个代表路径前缀的属性的元数据
    Reflect.defineMetadata('prefix', options.prefix || '', target)
  }
}

四. 参数装饰器 @GET @Post @Put @Patch @Delete

// 参数装饰器, GET, POST, BODY, DELETE, PATCH, PUT

// 导入元数据包
import 'reflect-metadata'

/**
 * @param target 类的原型: AppController.prototype
 * @param propertyKey 方法名: hello
 * @param desciprtor 属性描述器: 表示hello方法的属性描述器
 */
export const Get = (path: string = ''): MethodDecorator => {
  return (target: any, propertyKey: string, desciprtor: PropertyDescriptor) => {
    // 为desciprtor.value,也就是hello函数添加元数据path
    Reflect.defineMetadata('path', path, desciprtor.value)

    // 为desciprtor.value,也就是hello函数添加元数据method
    Reflect.defineMetadata('method', 'GET', desciprtor.value)
  }
}

使用模块装饰器

import { Module } from './@nestjs/common/module.decorator'
import { AppController } from './app.controller'

@Module({
  controllers: [AppController]
})
export class AppModule {}

使用控制器装饰器与参数装饰器

import { Controller } from './@nestjs/common/controller.decorator'
import { Get } from './@nestjs/common/httpMethod.decorator'

@Controller()
export class AppController {
  @Get('hello')
  hello(): string {
    return 'hello'
  }
}