使用TypeScript的装饰器实现简易Nest.js
在学习Nest.js的过程中, 让我很迷恋的两个功能:
- 依赖注入(DI)
- 控制器(Controller)
这两个都是依靠TypeScript
(简称:ts
)强大的装饰器(Decorator), 关于装饰器, 我在之前的文章讲过 TypeScript Decorators装饰器 有兴趣的同学可以去阅读一下, 加深对 装饰器 的理解;
依赖注入
在学习 依赖注入 之前, 我们得先了解什么叫 控制反转(Ioc, Inversion of Control)
维基百科
控制反转(英语:Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的[耦合度] 其中最常见的方式叫做依赖注入(Dependency Injection,简称DI 还有一种方式叫“依赖查找”(Dependency Lookup)
比如 Class A 中用到了 Class B的对象b, 在平时我们开发的时候,我们需要 去 初始化B的对象, 也就是 new B();
如果采用了 依赖注入之后, 只需要在 A的代码中 定义 一个 B的对象,不需要直接 new 来获取这个对象, 而是通过相关的容器控制程序来将B对象在外部new出来,并注入到A的对象中;
截取Nest.js的一个例子:
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('ab*cd')
getHello(): string {
return this.appService.getHello();
}
}
当我们需要 AppService
这个类的对象时, 我们并不需要去创建一个实例, 只需要引入即可, 这样 我们可以在 AppController
的任何地方都可以对这个对象进行操作, 减少了 代码之间的耦合;
简易代码
想起赵本山,宋丹丹的小品, 把大象装进冰箱需要几步? 三步
-
第一步: 打开冰箱门。
-
将
tsconfig.json
的配置:"experimentalDecorators": true, "emitDecoratorMetadata": true,
-
安装
reflect-metadata
,yarn add reflect-metadata
-
-
第二步: 把大象塞进冰箱; 编写代码
import 'reflect-metadata'
interface Contructor<T = any> {
new (...args: any[]): T
}
function Inject(): ClassDecorator {
return function (target) {}
}
class AppService {
sayHello () {
console.log('Hello World')
}
}
@Inject()
class AppController {
constructor (private readonly appService: AppService) {}
print () {
this.appService.sayHello()
}
}
function Factory<T>(target: Contructor<T>): T {
const providers = Reflect.getMetadata('design:paramtypes', target)
const args = providers.map((provider: Contructor) => {
return new provider()
})
return new target(...args)
}
Factory(AppController).print()
- 第三步: 关上冰箱门, 运行代码; 完美的输出了
Hello World
控制器
在 Nest.js 中 控制器 是用来 处理 和 响应请求的, 也就是 request
和 response
;
控制器的目的是接收应用的特定请求。路由机制控制哪个控制器接收哪些请求。通常,每个控制器有多个路由,不同的路由可以执行不同的操作。
为了创建一个基本的控制器,我们使用类和装饰器
。装饰器将类与所需的元数据相关联,并使 Nest 能够创建路由映射(将请求绑定到相应的控制器)。
import { Controller, Get, Post } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('ab*cd') // 处理 get 请求
getHello(): string {
return this.appService.getHello();
}
@Post('post') // 处理 post请求
postMethod(): string {
return 'Hello World';
}
}
简易代码实现
import 'reflect-metadata'
const MEHTOD_METADATA = 'method'
const PATH_MEDADATA = 'path'
/**
* Controller 装饰器
* @param path
* @returns
*/
function Controller(path: string): ClassDecorator {
return function (target) {
Reflect.defineMetadata(PATH_MEDADATA, path, target)
}
}
/**
* Methods 装饰器
* @param method
* @returns
*/
function createMappingDecorator (method: string) {
return function (path: string): MethodDecorator {
return function(target: Object, key: string | symbol, descriptor: PropertyDescriptor) {
Reflect.defineMetadata(MEHTOD_METADATA, method, descriptor.value)
Reflect.defineMetadata(PATH_MEDADATA, path, descriptor.value)
}
}
}
function isConstructor (item: string) {
return item === 'constructor'
}
function isFunction(fn: unknown) {
return typeof fn === 'function'
}
function mapRoute (instance: Object) {
const prototype = Object.getPrototypeOf(instance)
const methodsNames = Object.getOwnPropertyNames(prototype).filter((item: string) => {
return !isConstructor(item) && isFunction(prototype[item])
})
return methodsNames.map((name: string) => {
const fn: Function = prototype[name]
const route: string = Reflect.getMetadata(PATH_MEDADATA, fn)
const method: string = Reflect.getMetadata(MEHTOD_METADATA, fn)
return {
route,
method,
fn,
name
}
})
}
const Get = createMappingDecorator('Get')
const Post = createMappingDecorator('Post')
@Controller('/test')
class SomeClass {
@Get('/a')
someMethod (): string {
return 'Hello World'
}
@Post('/b')
somePostMethod (): string {
return 'Post'
}
}
const routes = mapRoute(new SomeClass)
console.log(routes)
这样我们就可以拿到一个 路由对象
这样的话,我们把处理过的路由对象, 交给 express
处理
代码如下:
// decrotor.ts
import 'reflect-metadata'
const MEHTOD_METADATA = 'method'
const PATH_MEDADATA = 'path'
/**
* Controller 装饰器
* @param path
* @returns
*/
export function Controller(path: string): ClassDecorator {
return function (target) {
Reflect.defineMetadata(PATH_MEDADATA, path, target)
}
}
/**
* Methods 装饰器
* @param method
* @returns
*/
function createMappingDecorator (method: string) {
return function (path: string): MethodDecorator {
return function(target: Object, key: string | symbol, descriptor: PropertyDescriptor) {
Reflect.defineMetadata(MEHTOD_METADATA, method, descriptor.value)
Reflect.defineMetadata(PATH_MEDADATA, path, descriptor.value)
}
}
}
function isConstructor (item: string) {
return item === 'constructor'
}
function isFunction(fn: unknown) {
return typeof fn === 'function'
}
export function mapRoute (instance: Object) {
const prototype = Object.getPrototypeOf(instance)
const methodsNames = Object.getOwnPropertyNames(prototype).filter((item: string) => {
return !isConstructor(item) && isFunction(prototype[item])
})
const parentPath = Reflect.getMetadata(PATH_MEDADATA, prototype.constructor)
const routes = methodsNames.map((name: string) => {
const fn: Function = prototype[name]
const route: string = Reflect.getMetadata(PATH_MEDADATA, fn)
const method: string = Reflect.getMetadata(MEHTOD_METADATA, fn).toLowerCase()
return {
route,
method,
fn,
name
}
})
return {
root: parentPath,
routes
}
}
export const Get = createMappingDecorator('Get')
export const Post = createMappingDecorator('Post')
// index.ts
import { Controller, Get, Post, mapRoute } from './decrotor'
import express, { Request, Response, Router} from 'express'
interface Route {
route: string;
method: string;
fn: () => void;
name: string
}
interface CustomRouter {
root: string,
routes: Route[]
}
@Controller('/test')
class SomeClass {
@Get('/a')
someMethod (): string {
return 'Get Hello World'
}
@Post('/b')
somePostMethod (): string {
return 'Post'
}
}
const app = express()
const retRouter: CustomRouter = mapRoute(new SomeClass) as CustomRouter
const router: Router = express.Router()
retRouter.routes.forEach((route: Route) => {
if (route.method === 'get') {
router.get(route.route, (req: Request, res: Response) => {
const value = route.fn()
res.send(value)
})
return
}
if (route.method === 'post') {
router.post(route.route, (req: Request, res: Response) => {
const value = route.fn()
res.send(value)
})
}
})
app.get('/', function (req: Request, res: Response) {
res.send('Home Hello World')
})
app.use(retRouter.root, router)
app.listen(3000)
这样我们就简单的实现了一个简易的Nest.js
转载自:https://juejin.cn/post/7072934852974084127