EggShellDecorators源码解析

准备工作:

1.首先fork一份代码到自己仓库并下载,主要用于边解析边做注释用 egg-shell-decorators

2.本地创建一个eggjs项目 用于分析参数及测试 npm init egg -type=ts

3.启动一个空白的ts项目 用于打印解析语法

解析开始

根据文档开始使用,router.ts里面引入EggShell 将app做参数传递执行EggShell

看下蛋壳源码 index.js 发现EggShell是一个类,并且里面只有一个for循环,循环的是一个Map实例 ctMap的value的遍历,此时这个ctMap是空的,所以暂时没有任何作用,我们发现ctMap跟MethodHandler有关系。

index.js文件引入了一个类并进行了实例化 *const* methodHandler = new MethodHandler(ctMap); 在文件结尾将这个实例的方法一一导出,所以估计应该是这个实例对ctMap进行了操作,增加了一些东西,继续往下看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
EggShell,

Get: methodHandler.get(),
Post: methodHandler.post(),
Put: methodHandler.put(),
Delete: methodHandler.delete(),
Patch: methodHandler.patch(),
Options: methodHandler.options(),
Head: methodHandler.head(),
Middleware: methodHandler.middleware(),

Prefix: ctHandler.prefix()
};

来到 method-handler.js文件,可以看到 MethodHandler 这个类定义了许多方法,get、post等方法使用的都是同一种方法。方法名使用symbol做了特殊处理,核心方法是 mappingRequest 方法。

mappingRequest 方法里面,path是装饰器使用时候传递的参数,照常理和使用文档,path是路由地址。mthod是不同方法的类型,比如get、post,使用get、post等方法的时候调用此函数,顺便将请求类型一起传递过去。

mappingRequest 这个方法做了什么事呢:

1.将target 方法体作为key和value放入了cMap

2.使用 Reflect.defineMetadata 将path和请求方法放入到装饰器装饰的方法中

cMap是公共变量,此时调用了诸如Get、Post的装饰器,其已经加入cMap

回到EggShell继续,开始for of 循环map

循环的每一项如下:

c的结构如下

c BaseContextClass {

​ pathName: ‘controller.home’,

​ fullPath: ‘/Users/xaviershi/Desktop/WEB/TS/eggjs-egg/app/controller/home.ts’

​ }

​ c BaseContextClass {

​ pathName: ‘controller.test’,

​ fullPath: ‘/Users/xaviershi/Desktop/WEB/TS/eggjs-egg/app/controller/test.ts’

​ }

第一行代码 *let* { prefix } = ctHandler.getMetada(c.constructor);

这里去 *const* ctHandler = new ControllerHandler(); ControllerHandler 所在的文件去看看

prefix调用了ctHandler.getMetada 方法,拿的是controller前后的 prefix

下面一行代码

1
2
3
const propertyNames = _.filter(Object.getOwnPropertyNames(c), pName => {
return pName !== 'constructor' && pName !== 'pathName' && pName !== 'fullPath';
});

// propertyNames 结构如下

// [ ‘index’, ‘eggTest’, ‘meTest’ ]

// [ ‘index’, ‘test’ ]

拿到的是每个controller里面的方法。

解析

Reflect.defineMetadata 这个方法接收三个参数

Reflect.defineMetadata(‘role’, name, target); 自定义元数据叫role,值是name,target是要添加元数据的那个类。

调用method.Get 嵌套了很多层,不过return的是每个方法的调用,所以实际还是走的有参数的方法装饰器那套。

在调用EggShell()方法的时候,对应的各个装饰器已经已经注入了(装饰器编译时调用),所以EggShell里面的方法都可正常执行。

疑问

1.为什么要用symbol处理一下方法名

2.EggShell的运行时机 EggShell是在最开始调用的,然后里面循环了map, get等方法是在EggShell里面的controller里面的方法上面装饰的,然后再往map里面set的值,为什么EggShell初始就能循环到值

资料

JS 装饰器,一篇就够

TypeScript:理解 Reflect Metadata

eggjs结合swagger

egg-swagger-ui 小白文档

接口文档eggjs和swagger配合+YAPI

Eggjs 搭配 Swagger 和 Sequelize 编写接口文档 上手教程

Author: XavierShi
Link: https://blog.xaviershi.com/2020/08/11/EggShellDecorators源码解析/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.