一起学react(2) dva底层源码与案例分析
本次主要针对antd-pro案例来进行分析
用到的技术栈: dva dva-core antd-admin
之前写的一篇dva入手教程 反响挺不错的 今天就来点了解一下dva的核心代码
本人QQ:469373256 如果有问题 可以加我QQ
你可以使用集成化的命令行工具 ant-design-pro-cli。
$npminstallant-design-pro-cli -g
$mkdirmy-project&&cdmy-project
$ pro new# 安装脚手架
项目初始化部分
![](https://img.haomeiwen.com/i5746917/f872292b2a94540c.png)
一步步的来进行讲解 先看一下 接下来的代码分析 将会按照编号的顺序一点点往下讲
#1:dva初始化部分 源码地址:dva/packages/dva at master · dvajs/dva · GitHub
![](http://upload-images.jianshu.io/upload_images/5746917-ae28dbb32284902a.png)
这里主要是看几点
1.首先history部分 如果你没有传进去history的话 会默认给你使用HashHistory
2.你原来所有传进去的参数 又会被转换成createOpts 这部分 现在还用不到 在之后会讲到
3.这里通过将构造好的createOpts传递给core以后 返回了一个app 但是这里要注意的是 刚才第一个页面的时候 用到的router跟start其实都是在这里进行定义的dva-core本质是没有返回这部分数据的
#2: model加载部分 源码地址:dva/index.js at master · dvajs/dva · GitHub
model部分的代码 都是在dva-core里面的
这里这个model有点小小的区别
如果你这个app.model是在app.start之前调用的会 走的是这个
![](http://upload-images.jianshu.io/upload_images/5746917-b41de478caca8e37.png)
而如果你在app.start之后调用的话 会走这个 一般用在按需加载的视图上面
![](http://upload-images.jianshu.io/upload_images/5746917-e886957e24c46587.png)
先针对第一个model做一下分析 第一个model代码很少 第二个model部分会在start以后进行讲解
其中checkModel是用来判断是否已经加载过对应的model了
prefixNamespace 这个是帮你构建一下函数对应的名字 比如你的app.model.effect下面有个*getapp({payload},{select,put,call})
这样的函数名字 那么 通过这个prefixNamespace转换以后 就会变成app/getapp
帮你进行了一下转换 附上对应的源码
![](http://upload-images.jianshu.io/upload_images/5746917-2dda515d02b8e7b5.png)
#3 router加载部分 源码地址:dva/index.js at master · dvajs/dva · GitHub
![](http://upload-images.jianshu.io/upload_images/5746917-5d1077d9347ed6ca.png)
router部分是在执行了app.start以后才会被触发的 所以这里是看不到的
但是可以提前了解一下
![](http://upload-images.jianshu.io/upload_images/5746917-51cebc697d7b2678.png)
如果这里的app.start(container)有值的话 会直接调用render来进行渲染 这里就是将刚才的app._router传递了过去 记得 刚才这个是有判断的 这个app._router必须是一个function 否则是会有问题的 再来看看具体的传参
![](http://upload-images.jianshu.io/upload_images/5746917-47adf59b9e2e2ea9.png)
这里其实就很明显了 刚才的那个app.start(container)这个变量其实会被react-dom进行使用 具体可以看react-dom的文章 这边不细说
来看看关键的getProvider 这里你可以发现 我们刚才用到的app._router被传递了进来 并且变成了Provider的children来实现
那么再进一步 去看看这个app._router究竟干了什么
这部分的代码比较多 先贴主要部分的
![](http://upload-images.jianshu.io/upload_images/5746917-278427bfd0affbbe.png)
这里可以看到我们刚才传递进来的这个app跟history这两个都非常重要 在后面会使用到
![](http://upload-images.jianshu.io/upload_images/5746917-ed29aca61bfbbcd5.png)
这里单个的route就不说了 我想你们比我也清楚
主要看看这个routes部分
![](http://upload-images.jianshu.io/upload_images/5746917-1bc655c7bc8a00aa.png)
从这里可以发现 除了path参数部分 其他的都被涵盖到了...dynamics这个里面 这是es6的语法 具体可以了解一下阮一峰的es6
有一点了解过route这个组件的话 其实会知道 这里是通过path去加载对应的组件的 并不是这边map了以后 就全部都被加载了
看一下react-router-dom 中switch部分的代码
![](http://upload-images.jianshu.io/upload_images/5746917-9a59da3ceb6b8dce.png)
这里可以清楚的看到 如果匹配才会调用cloneElement返回视图 否则仅仅只返回一个null而已
继续往下说 我们现在假设我们的组件被符合调教 需要被加载了 那么我们的model又是什么时候被加载进去的呢 这里得深入的去看一下这个dynamic 这是一个高阶组件
![](http://upload-images.jianshu.io/upload_images/5746917-68fa01966cffa186.png)
这里的设计非常巧妙得一步步的看下去
![](http://upload-images.jianshu.io/upload_images/5746917-d766419ddaeb2ff7.png)
1.这里对config传进来的参数做了一下重命名
2.如果你的config里面 有传进来resolve的话 默认就会调用你传进来的那个 这样你可以更加的自定义一点 如果没有的话 就返回默认的函数
3.判断model是否是有内容 如果没有返回一个空数组 并且直接调用resolveComponent返回对应要渲染的视图
4.判断model内容 如果不符合 就直接抛出去
5.如果内容符合的话 就遍历加载app.model进行加载 所以model是在这里被进行加载的
6.如果你想对LoadingComponent进行自定义的话 你就可以通过在config里面传参的方式
接下来看看关键的asyncComponent部分代码
![](http://upload-images.jianshu.io/upload_images/5746917-8828eed4540eadbf.png)
1.初始化loadComponent与asyncComponent 这里loadComponent是可以自己指定的 只要你的config里面有带上就可以自定义了
2.开始加载load视图
3.首先在load这边进行resolve的时候 因为promise是一个异步也就是一个微任务的关系 所以有可能会出现在render之前运行 所以在之后对this.mounted进行了判断 如果已经到了didMount中的话 说明已经走了render 所以必须用setState来刷新 而反之 没有经过render的话 这里不需要用setState来刷新 只需要直接赋值就可以了 然后在render里面就可以获取到对应的最新数据了
ok router部分讲完了 接下来看start部分
#start部分
dvacore源码地址:dva/index.js at master · dvajs/dva · GitHub
dva源码地址:dva/index.js at master · dvajs/dva · GitHub
这个start比较特殊 它其实有两个地方在执行 一个是dva 一个是dva-core
dva部分:
![](http://upload-images.jianshu.io/upload_images/5746917-edb8d34000761553.png)
这里主要看oldAppStart部分 这里调用的就是dva-core中的start
其他剩余的部分 在上面就已经讲了 就不多说了
核心部分代码
start部分代码比较多 而且比较杂 首先你要知道一件事情 就是dva是这么实现的 dva本质上有几个中间件router,saga,promise
dva并没有产生新的东西 而是基于redux对于中间件的分散方式的一种封装 使其能按照一定的规范去进行编写也就是model部分 ok 现在有了这个中间思想 在去看一下dva.core中start相关的代码
![](http://upload-images.jianshu.io/upload_images/5746917-15d666337b203d3c.png)
1.初始化全局错误输出 在之后的所有getSaga里面 都会将这个onError作为参数带过去 以便获取到saga运行时的错误信息 并且会反馈到最外部的全局错误输出那边
![](http://upload-images.jianshu.io/upload_images/5746917-e3b80c9e6bd83089.png)
2.3 :创建saga与promise中间件 并且对getSaga进行初始化 这个在之后会用到
![](http://upload-images.jianshu.io/upload_images/5746917-316b58987eeabddf.png)
4.
初始化一个saga变量 用于存放所有model中的effect副作用 在之后会用到
初始化外部提供的reducer在后面会进行合并 这里保留原生redux的功能
将model中存在的reducer与saga进行合并
6.14
![](http://upload-images.jianshu.io/upload_images/5746917-63808a207a13566d.png)
![](http://upload-images.jianshu.io/upload_images/5746917-9d06c7833f84d6c2.png)
可以看到 如果你的key是onReducer的话 会走到下面的getOnReducer 如果你对dva的option中 有设置onReducer这个字段的话 会将里面的所有内容执行一次返回最新的reducer
如果没有的话 就是有啥就返回啥了
![](http://upload-images.jianshu.io/upload_images/5746917-c3b28e3b643ccde8.png)
combineReducers这个没啥好说的 用过redux就知道这个是干啥的
7.createStore这里比较简单 一笔带过了
![](http://upload-images.jianshu.io/upload_images/5746917-e11d1e86c8d8e822.png)
8.
![](http://upload-images.jianshu.io/upload_images/5746917-91c9205605de85ad.png)
这两句代码会在之后的model中被用到 刚才有说过 如果start以后 还需要加载model的话 就需要这两个了
9.
![](http://upload-images.jianshu.io/upload_images/5746917-048232b87d00b2f7.png)
这里是用来全局监听state改变的地方 如果对redux熟悉的话 你的每次dispatch都是会触发回调的 所以这边会实时的传递最新得state
10.
![](http://upload-images.jianshu.io/upload_images/5746917-05348b94f7e729fb.png)
还记得刚才将所有model的数据 放到一个数组的步骤吗 现在这里就可以直接拿来用了
![](http://upload-images.jianshu.io/upload_images/5746917-9d8f3f2c8538821e.png)
11.
dva-core中代码
![](http://upload-images.jianshu.io/upload_images/5746917-0c23d3c62dd1038f.png)
dva中代码
![](http://upload-images.jianshu.io/upload_images/5746917-e3019f2fc04a10ab.png)
![](http://upload-images.jianshu.io/upload_images/5746917-13847f1dcd628a8a.png)
这个代码的作用就是触发一个history的监听 至于history是什么 只要看一下初始化部分的代码就知道了
12
![](http://upload-images.jianshu.io/upload_images/5746917-fd48c4459bb65263.png)
![](http://upload-images.jianshu.io/upload_images/5746917-3568d6150ac0413e.png)
![](http://upload-images.jianshu.io/upload_images/5746917-c35353511e9e3780.png)
刚才setupApp开启了history的监听 那么这里就是把所有model的subscriptions跑一遍 可以看到sub那边传递过去的值 就是model里面能获取到的东西
13.
![](http://upload-images.jianshu.io/upload_images/5746917-f4e7e21874601eb5.png)
修改原有的model只想 使其指向一个新的地址 来看一下具体实现
![](http://upload-images.jianshu.io/upload_images/5746917-42bee3d442fd0c90.png)
这里采取的方式是 如果是reducer部分 则采取直接使用redux自带的replaceReducer进行全部的替换 这里成本其实还是蛮大的
如果是effect部分的话 就获取对应的getSaga然后全部执行
subscriptions部分跟刚才说的那个一样不多讲了
![](http://upload-images.jianshu.io/upload_images/5746917-792f3d2d23f31838.png)
![](http://upload-images.jianshu.io/upload_images/5746917-8e2f4a58b81abcf5.png)
简单来说就是 遍历所有的effect并且生成一个getWatcher通过fork的方式进行执行 通过这样的方式 所以你通过app/xxx的时候 才能直接获取到 因为这个会一直在运行 这里要注意是一直在运行 除非你使用了unmodel卸载了对应的model 也就是下面的一行代码
![](http://upload-images.jianshu.io/upload_images/5746917-242b90aedeb850aa.png)
除非接收到了这个消息 否则会一直执行 这里其实还是比较占用内存的一个地方 看看卸载部分
![](http://upload-images.jianshu.io/upload_images/5746917-d0b66ac7cbb47ec1.png)
至此整个dva源码 分析完毕 如果有写的不对的地方 麻烦加我QQ:469373256 告知一下 多谢!!