今天我们来讨论一下为什么有些vuex里的state需要使用computed进行包装,而有些则直接定义在data中就可以使用
问题展示
<body>
<div id="app">
<div>{{count}}</div>
<div>{{d.count}}</div>
<div>{{$options.render}}</div>
<button @click="addCount">点击增加</button>
</div>
</body>
Vue.use(Vuex);
const state = {
count: 1,
};
const mutations = {
addCount(state) {
console.log("mutation");
state.count++;
},
};
const actions = {
addCount({ commit, state }) {
console.log("action");
commit("addCount");
},
};
const store = new Vuex.Store({
state,
mutations,
actions,
/** trict: true*/
});
new Vue({
el: "#app",
store,
data() {
return { count: this.$store.state.count, d: this.$store.state };
},
methods: {
addCount() {
// this.d.count++;
this.$store.state.count++; //没有开启严格模式不会报错
// this.$store.dispatch("addCount");
},
},
});
从以上代码来看当我点击按钮时addCount方法会被调用从更新了state中的count的值,这时候页面上会显示什么呢?是一个2还是两个2呢。答案是第一行的<div>{{count}}</div>标签将显示1,而第二行的<div>{{d.count}}</div>将显示2。
为什么会出现这样的问题呢?在这里呢我先给大家讲解一下整个页面的初始化依赖初始化过程。
依赖初始化过程
我们知道vue初始化的时候会依次初始化initLifecycle、initEvents...initState等等方法。其中initState中又会初始化initProps、initMethods、initData等等。我们着重看一下initData方法
function initData (vm: Component) {
let data = vm.$options.data
// 可以当做在这里第一次取值
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// ...省略部分代码
// 对象劫持核心方法
observe(data, true /* asRootData */)
}
// 以下是vuex的state响应式的实现
store._vm = new Vue({ data: { ?state: state }, computed })
vuex的响应式实现是直接使用了Vue的响应式机制,在new Vuex.Store()时state就已经完成的响应式的设置,此时state中的所有属性里的Dep对象中的subs属性都是空数组subs: Array<Watcher>; = []。这就意味着此时state中没有任何能够触发的观察者函数。
当代码执行到new Vue({el:'#app'}) 时初始化vue传入的data数据格式为{count: 1, d: this.$store.state},此时this.$store.state.count这段代码将会触发两个get函数分别为state、state.count。但是此时该组件并未挂载渲染所以这两个get函数不会收集到组件的watcher渲染观察者,因为此时Dep.target为undefined。所以count就被赋值为1,再看属性d其指向state的内存所以d被赋值为state的引用
紧接着到了$mount时渲染观察者watcher被创建了,它被赋值到了Dep.target中并调用了render函数。在render中dom被解析成了这样function anonymous( ) { with(this){return _c('div',{attrs:{"id":"app"}},[_c('div',[_v(_s(count))]),_v(" "),_c('div',[_v(_s(d.count))]),_v(" "),_c('div',[_v(_s($options.render))]),_v(" "),_c('button',{on:{"click":addCount}},[_v("点击增加")])])} }, 当执行该方法时又会对count 和 d.count进行取值。 count会收集到当前渲染依赖d也会收集到,那d.count呢?它是指向state.count的所以在取值时会触发state和state.count的依赖收集将渲染观察者收入各自的dep中。所以这就解释了为什么一个能响应一个不能的原因了
Computed响应式
有些同学有疑问为什么我写成这样他就能响应式呢?
new Vue({
el: "#app",
store,
data() {
return { d: this.$store.state };
},
computed: {
count() {
return this.$store.state.count;
}
}
methods: {
addCount() {
console.log("addCount");
// this.d.count++;
this.$store.state.count++;
console.log(this.$store.state.count);
// this.$store.dispatch("addCount");
},
},
});
因为 this.$store.state.count 是一个基础数据类型所以在 initDate 时已经变成常量值, 导致后续无法在触发 this.$store.state.count 的依赖搜集。所以需要使用 computed 创建一个watcher 并触发 将渲染watcher收集到 this.$store.state.count 的dep 中, 这时当修改 this.$store.state.count 值时,会触发计算属性watcher将dirty = true, 随后触发渲染watcher获取到最新的值完成渲染