前言
- 本文目标:代码层面完成一个初版的vuex
- 本文为思路篇,主要指出阶段目标,以需解决问题和解决思路的角度讲解原理,文末附上具体实现文章链接,建议两个tab页同步对比理解
完成功能
- vuex管理全局数据,每个组件均共享同一数据
- vuex数据的改变同样会触发视图更新(前提是在页面上被使用,不理解为什么建议阅读拙作(vue1.0版)数据观测 依赖收集)
- 单一数据流管理:data-》actions-》mutations-》data
第二版待完成功能
- vuex数据模块化
- vuex插件机制
- 辅助函数map系列(mapState、mapGetters、mapMutations、mapActions)
第一阶段:实现全局数据管理
先看使用
## store.js
state: {
count: 100
},
## App.vue
<div id="app">
{{$store.state.count}}
</div>
核心问题
- 如何使得每个组件上都有$store属性
解决思路
- 采用mixin+深度遍历的方式
- mixin中使用beforeCreate钩子,这样就可以在每个组件中执行一段逻辑
- 逻辑如下:判断是否为根组件,即$options(用户传递vue的对象)上有无store
- 是,则挂载$store属性为store
- 否,挂载store属性
第二阶段:实现数据劫持,vuex数据改变也会触发视图更新
核心问题
- 此时只是相当于定义了一个全局属性,问题是根本未被vue托管(即未放置在data属性中),数据改变不会触发视图更新
解决思路
- 既然插件中可以获取到vue的构造(install方法的参数),那就直接new Vue然后将state挂载到data上即可
- 注意,此时因为state是对象,也就是引用传递,即共用同一块地址值,所以改变store上的state属性也会触发data中的state的改变,从而视图更新
注意:可以看到,vuex的实现原理就是new Vue,这是vuex强依赖vue的原因,也是与redux最大的不同
第三阶段:实现getters
先看使用
## store.js
getters: {
value(state){
return state.count + 100
}
},
## App.vue
<div id="app">
{{$store.state.count}}
{{$store.getters.value}}
</div>
核心问题:
- 实现getters的取值正常,即函数返回值为其取值
- 实现更改getters触发视图更新
核心思路:
- 初始化时为每个getter定义defineProerty,取值时执行其对应函数,将函数返回值返回
- 借用vue中的computed,将getter定义在vm上;
- 其实vuex中的getters就是对应vue的compouted,一是计算属性,二是可缓存
- 取值的逻辑也得改为直接从vm实例上获取 这样才能实现依赖收集,不理解的话建议阅读拙作vue|源码篇|watch及computed的实现
第四阶段:实现更改数据流程
先来看看vuex官网的流程图
由state出发,我们已经实现了state-》Vue components这个render阶段,我们可以发现,在vue中提倡组件中触发actions,再由actions去触发mutations更改state,即达到更改数据单一入口的设计(mutations)
### store.js
mutations: {
add(state){
state.count += 1;
}
},
actions: {
add({commit}){
commit('add')
}
},
### App.vue
methods: {
clickMe(){
this.$store.dispatch('add')
}
}
核心问题
- 实现api
- this问题,在actions中执行commit方法,若不处理其this将指向undefined
解决思路
- store类上定义commit、dispatch方法,分别执行store上的mutation、action
- 考虑原型链,在构造中再次定义commit、dispatch方法,内部以call执行原型上的commit/dispatch绑定this