Vue变量只在template中使用才会收集依赖吗?

524 阅读2分钟

疑问:data中的变量只有在template或者render中使用才会收集依赖

构造demo

先构造一个最简单的demo

import Vue from "vue";
new Vue({
  data: {
    count: 0,
  },
  render() {
    return (
      <div>
        <h1 onClick={this.add}>{this.dcount}</h1>
      </div>
    );
  },
  computed: {
    dcount() {
      return this.count * 2;
    },
  },
  methods: {
    add() {
      this.count++;
    },
  },
}).$mount("#app");

每次点击修改count视图都会更新,render函数中并没有使用count, 显然这个说法不正确。 那么怎么解析这个demo呢?

原因分析

通过断点调试vue,我们可以知道new Vue中的一个流程,上面的demo主要分析流程

  • initData 通过defineProperty设置count的getter、setter
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value
    },
   });
  • $mount('#app') 实例化渲染类型的watcher,立刻求值,执行render函数,此时会触发dcountgetter
  new Watcher(vm, updateComponent, noop, {
    before: function before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate');
      }
    }
  }, true /* isRenderWatcher */);
  
   // updateComponent() ---> render()
  // renderWatcher, 会立刻求值this.get();
  // pushTarget
  // Dep.target = renderWatcher;
  // targetStack入栈 此时 targetStack = [ renderWatcher ]
  render() {
    return (
      <div>
        <h1 onClick={this.add}>{this.dcount}</h1> // 读取dcount,触发下面的computedGetter
      </div>
    );
  },
  • initComupted 构造一个computed类型的watcher,它不会立刻求值,在执行render时候才会去求值, watcher的dirty属性此时是true
function computedGetter () {
    var watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) { 
       // 渲染watcher执行render的时候,触发computedGetter
      if (watcher.dirty) {
          // targetStack入栈, 此时Dep.tatget = watcher
          // targetStack = [ renderWatcher, watcher];
         // 触发watcher.get 实际上就是dcount函数
        watcher.evaluate();
        // tagetStack出栈,
        // 此时Dep.target = renderWatcher
        // targetStack = [ renderWatcher ]
        // 计算结束后设置dirty为false, 这样子即使template中多次出现的dcount,
        // 不需要重复计算,直接返回结果
   
      }
      // 这里十分关键,把renderWatch混入
      if (Dep.target) { // 此时为renderWatcher
        // 这个函数就是遍历watcher中的deps,把renderWatcher push到subs
        // 因此count属性多了一个renderwatcher,每次修改count都使renderWatcher.update
        watcher.depend();
      }
      return watcher.value // 返回结果
    }
  }
  • dcount依赖count,触发count的getter,当前的Dep.target是dcount实例的comupted-watcher
   // 读取count,触发getter
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        dep.depend(); // 把Dep.target push到dep的subs
      }
      return value
    },
   });

流程图

reactive.png

当我点击h1标签的时候,读取this.count会触发countgetter, 可以看到subs中存在两个watcher.

dep.png

总结

上面除了这种常见外,如果主动使用watch,如下

  • count属性的依赖dep中就包含了一个依赖, 因为使用watch也会创建一个watcher,且把watcher主动push到subs中。
import Vue from "vue";
new Vue({
  data: {
    count: 0,
  },
  render() {
    return (
      <div>
        <h1 onClick={this.add}></h1>
      </div>
    );
  },
  watch: {
    count() {
        
    }
  },
  methods: {
    add() {
      this.count++;
    },
  },
}).$mount("#app");