Vuex源码中为什么要一个Vue实例,并只有用它才能实现视图的刷新?

1,066 阅读3分钟

在看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方法,从而实现视图更新。