背景
首先我们要明白为什么需要vuex? vuex的出现是为了解决复杂情况下组件数据共享(即全局数据)的问题。将一些数据从组件级别上升到应用级别,而且数据还是 响应式的,即修改了全局数据,页面相应内容被刷新,如果用本地存储的方案数据就不是响应式的。另外vuex还实现了数据的 模块化 。
说到响应式,是不是会联想到vue? vuex的核心跟vue有很大关系,这个是vuex设计的巧妙之处。
Vuex可以理解为是个全局的数据仓库,存放应用级别的数据,让组件共享数据,数据的修改只能通过mutation, mutation是同步操作,如果我们经过一些异步操作(发送ajax请求),可以用action包装这个过程,在action里面提交mutation进行数据变更。
图解版原理
文字版原理
理解原理我们先从vuex使用开始,流程是:
- 先引入Vuex和Vue, 然后Vue.use(Vuex)
- 实例化Vuex.store({})获取一个store实例, 传入的对象键名可包括modules/state/getters/mutaions/actions, modules的值是对象,每个键值对的值可以是个也是包含键名state/getters/mutaions/actions的对象
- 将这个store实例注入new Vue({})的配置中
下面我们来逐一分析。
对应上面第一步,Vue.use(Vuex),Vuex作为vue的插件,这里执行的是安装过程,use是Vue的一个全局静态方法,会调用插件的install方法,源码中install是通过Vue.mixin方法将一个vuexInit函数混入到所有vue实例的beforeCreate钩子函数,赋值每个vue实例的$router,如果实例化过程中$option传入了router,则将这个router赋值给vue实例的$router属性,否则找vue实例父组件的$option上是否有router。在上面第三步,我们知道我们是将全局的store实例注入给了vue根实例,构建vue组件树是从根实例开始,因此每个子孙组件实际上都是从根实例上拿到全局的store实例。这也就解释为什么我们能在每个组件中通过this.$router去访问同一个对象。
对应上面第二步,实例化Store的过程,执行了Store类的构造函数,主要流程有3步:
- 构建模块树,实例获得_modules属性,值是树状结果的模块Collection,关键代码this._modules = new ModuleCollection(options)
- 安装模块, 通过上面获得的模块树,store实例获得_actions, _mutaions, _wrappedGetters属性, 即store通过递归模块树获取到全部模块的actions/mutaion/getters, 而且根模块(_modules.root)上的state获取到了子孙模块的state, 构建了state树, 关键代码installModule(this, state, [], this._modules.root)
设置store的vm, 实例获得_vm属性,关键代码resetStoreVM(this, state),传入的state是上面提到的根模块的上的state树。
这个store实例的vm属性,是个vue实例,将根模块的state赋值到这个vue实例data上,将根模块的_wrappedGetter赋值到这个vue实例的computed上。对外暴露通过this.$store.state访问到state, 通过this.$store.getters访问到getter。
state的访问实际上是设置了store的原型属性state的getter函数,返回了store._vm._data.$$state; getter的访问实际上是返回了定义在这个store._vm上的key对应的值,因为module中的getter函数最后被作为computed属性去实例化这个vue。vue在初始化comoputed的时候会在实例上定义对应的属性,所以能通过vm.a访问到computed{‘a’:function(){}}中a的值vue的data和computed本身是响应式对象,所以state也是响应式的。 这就回答了我们最上面提到的原理。
核心代码见下面。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25function resetStoreVM (store, state, hot) {
// bind store public getters
store.getters = {}
const wrappedGetters = store._wrappedGetters
const computed = {}
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
computed[key] = () => fn(store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
// use a Vue instance to store the state tree
// suppress warnings just in case the user has added
// some funky global mixins
const silent = Vue.config.silent
Vue.config.silent = true
store._vm = new Vue({
data: {
$$state: state
},
computed
})
语法糖
mapState
mapGetters
mapActions
mapMutations
- 传入命名空间
- 支持数组和对象映射
- 对象展开符混入computed(mapState/ mapGetters) & methods(mapActions/ mapMutations)
1 | export default{ |