准备工作:
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 | module.exports = { |
来到 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 | const propertyNames = _.filter(Object.getOwnPropertyNames(c), pName => { |
// 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