在看vuex源码时,有一个问题困扰着我,为什么vue当中要实例化一个Vue,即执行这段代码:
store._vm = new Vue({
data: {
?state: state
},
computed
})
我们在使用vuex时,一般是将store的state或着getters以computed的形式注入到我们的组件配置中的,那它是怎样进行依赖收集,即当store里边的属性值发生变化时是怎样触发DOM更新的??
带着这两个问题,我开始了vue源码的单步调试...
问题1: 为什么要new一个Vue,不new不行吗?
先来看一篇大佬写的vuex分析的 文章
在这篇文章里,大佬用了一个简单的例子来说明vuex的基本功能:
看完这个demo,以及这句 其实上述部分就是Vuex依赖Vue核心实现数据的“响应式化”。,我一脸懵逼,它到底是怎样依赖Vue核心实现响应式的啊啊?源码好难,我要放弃😄
然后我做个了小实验,代码如下:
const globalData = {
msg: 'Hi Vuex'
};
Vue.prototype.globalData = globalData;
const vm = new Vue({
el: '#app',
});
console.log(vm);
然后...
结果是显然的,更改globalData的属性值,页面妥妥的不会发生变化。啥原因呢??
原来是渲染watcher中依赖为空那new一个Vue为啥就可以收集依赖了呢?在一波眼睛都快看瞎的debug操作之后,总算找到了问题的根源
function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
//...
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
//...
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
最关键的地方:
我们在执行new Vue时,传入的options中的data会被遍历递归转化为getter和setter,而这两是惰性的,即取值的时候才调用getter,设置的时候调用getter,而Dep是一个全局的变量,Dep.target自然也是。当把globalData赋值给Vue的prototye之后,new Vue的实例自然可以访问到globalData, 因为globalData和new Vue data中的globalData是相同的引用,那此时就会触发globalData相应属性的getter,而此时的Dep.target是后一个new Vue的渲染watcher,所以dep就被添加到了该实例的渲染watcher的deps中,这样在属性值发生变化时就能通过setter触发DOM更新了。(恕我愚钝,这样简单的弄了半天才搞明白😄)。那到底vuex中是不是一定要new一个Vue才可以呢,就state来说,仅使用Vue.util.defineReactive就可以了,但除了state之外,还有getters需要借助Vue的computed才可以,所以new一个Vue还是不可或缺的const globalData = {
msg: 'Hi Vuex'
};
const defineReactive = Vue.util.defineReactive;
function walk(obj) {
Object.keys(obj).forEach(key => {
defineReactive(obj, key);
})
}
walk(globalData);
new Vue({
beforeCreate() {
this.globalData = globalData;
},
el: '#app'
})
问题2: Vuex是怎样做到store变化让Vue更新视图的?
明白第一个问题之后问题2就迎刃而解了,获取store中的数据时触发getter,将组件的渲染watcher添加到store属性dep的subs中,从而实现依赖收集,在store属性值发生变化时执行dep.notify(),遍历该subs中的watcher,执行watcher的update方法,从而实现视图更新。