题目: Vuex源码解析
前言:vuex在vue生态中有着举足轻重的地位,它是vue项目在做集中式的状态管理时的首选,vuex的优势是可以做集中式的状态管理,也就是说如果你有一个状态需要在全局使用,并且希望在任意组件都可以对状态进行管理。那么vuex绝对是最佳的一个选择。那么这一篇文章我们将好好阅读一下vuex的源码,感受一下vuex的各种api究竟是怎么实现的。那么就开始吧
一、目录分析
1.源码下载
首先从git下载源码
git clone https://github.com/vuejs/vuex.git
下载源码过后会看到这样的一个目录结构
2.目录解析
dist目录下是生产环境执行的代码,src目录下是模块化开发时开发环境下的代码,为了更方便阅读我们直接看src下的代码。
我来解释一下每一个文件是用来干什么的,其中index.js是入口文件,导出了全局的vuex对象。此外store.js是创建了一个store类,module是创建moudleCollection 和 module类的,helpers是创建mapState、mapGetters等语法糖函数的。mixin是进行全局混入$store的文件。utils是一些工具函数。plugins是当vuex注册某些插件时的处理文件。
二、源码详解
1.index.js
在Index.js当中导出了很多对象,其中包括很重要的一个对象,那就是Store,记得在使用vuex的时候,我们是这样使用的。
import Vuex from 'vuex'
const store = new Vuex.Store({
state:{
name:'小鹏'
},
mutations:{
setName(state,payload){
state.name = payload
}
}
})
new Vue({
store,
render:h => h(App)
})
因此我们可以肯定的是Store一定是一个类,并且这个类接受一个参数,这个参数就我们写入的那个option对象。接下来我们就可以解析一下这个store类。
2.store.js
每当我们在进行new Store的时候,会创建一个store实例对象,在这个实例对象身上会绑定很多的属性,包括_actions 、_mutations 、_modules 并且在实例自身身上挂两个方法分别是原型上的dispatch 、commit方法,并将函数内部的this指针强行指向当前创建的store对象。
然后去初始化module ,并对store身上的state进行响应式化。以上就是store要做的事情。但是我们有很多细节并未谈起,比如可以看到源码中的_modules属性是赋值了一个ModuleCollection的实例对象。在创建moduleCollection实例对象时,传入了option,而_modules也是真正存储了状态的对象。面就来看一下ModuleCollection(option)做了什么吧!
3.module
每当new ModuleCollection()时就做了一件事情,调用原型上的register( [ ] , rawRootModule , false) ,所以register才是真正的核心函数。
默认情况下会先创建一个Module的实例,也就是说,默认情况下我们有一个名叫root的模块就是我们的根模块,这个根模块在store的_modules属性中是以 { root : module } 这样的形式存在的。
这个地方会有一个递归,当我们的根module存在modles属性时,也就是说具备子模块时,那么会继续对其进行register然后添加在Store的_modules身上。这样就可以把所有的模块通过键值对的方式维护在了_modules中。而创建一个module才是真正我们store的重点我们来看一下吧。
在这个每一个module实例中我们都会维护一个属于自己的模块,以及state。并且这个state还可以是一个函数或是一个对象,但在大多数情况下,它应该是一个对象。
好了先我们来小结一下。
无论是ModuleCollections还是Module其实本质上都共同的了一件事情,在Store实例的_modules属性身上添加了一个又一个对象,这些对象都是Module的实例,但至少又一个root的对象。
然后看完了modlue模块,我们就要来看一下。初始化根实例的方法。
注册模块的时候,对每一个模块都做了处理,首先肯定是根模块,就拿mutations来说,做的处理内容就是往store身上_mutations上映射每一个mutations函数,在registerMutation函数中是有体现的。
因此当函数执行完之后,store身上的mutations属性肯定是这样的数据结构
store:{
_mutations:{
setName : [ fn ... ]
}
}
并且fn在调用时其中的this是指向store实例对象的。此外会传入一个local.state,和payload,这就是为什么我们在使用mutation时,会接受一个state和一个payload参数。
mutations:{
setName(state , payload){
state.name = payload
}
}
但是为什么传入的state就是状态中的state呢,其实是因为它使用的是local.state在这里它其实是对store做了一下代理。
local.state实际上是访问的store.state。在这里就有个疑问啦,好像store身上并没有state的竖向呀,这就是接下来响应式化state需要做的事情。
在这里做的事情,就是让state变为响应式的对象,并且将_vm属性赋值为一个Vue实例对象,因此只要经过这一步的处理那么今后我们再次触发状态的更新就可以进行页面的更新,否则我们只不是是单纯的对状态做了更改,而不会触发视图的更新。
但是各位可能会好奇一个问题,store身上没有state属性呀,那我们刚刚降到在installModle时,local返回的就是store.state呀。那这个怎么解释呢?
这是因为
在Store的原型上也是做了一个对state的代理,当访问store时就将其指向this._vm._data.state了,因此访问store.state实际上访问的是响应式的对象。
4.插件
以上便是store的大概原理,就是说store是一个全局的对象,里面的状态依然是通过new Vue进行响应式化的,并且维护了一个_modules进行管理所有的模块,并且store提供了数据的记录,在这里就不一一进行介绍了。
而怎么让store进行全局的共享呢,还是要得益于混入,我们来看一下。
可以看到,这就是Vue的install函数,当使用Vue.use(vuex)的时候,就会执行这个函数。这里会判断属于那个版本。然后如果是根实例那么就在当前实例身上添加一个$store,然后值是全局的store对象。如果不是根实例就将praent的$store赋值为当前实例的$store,程序执行完毕后,每一个实例自身就都具有$store属性了,也正因为如此我们在每一个实例身上都可以访问到全局的store对象,并改变其中的状态。
以上便是store的原理了。