Skip to content

分支切换的实现原理 ​

本节目标:理解“分支切换的实现原理”的核心思路,并能把它用于实际开发或面试表达。 Effect是一个函数,它首先会执行一次,执行的时候就进行了一次依赖收集; 这次的依赖收集中用到的依赖数据的更新,肯定会触发找到effect,并调用它的run方法; 到此,已经实现了响应式,即数据变化,重新执行run方法; 但是,此时执行run方法时,是一些数据发生了变化,代码可能走了不同的条件判断,执行所依赖的数据可能已经变化了,也就是说有一些数据项我们可能不依赖它了,它的变化,我们不要再更新了! 所以,我们在执行run方法前,把之前可能无效的依赖收集清空,并在此次run的时候,记录下此次依赖的数据项,也就是重新做依赖收集,这样,我们才能在这次用到的依赖项更新时,重新运行run;

原理:

effect传入的函数,会首先执行一次,在执行的过程中收集了一次依赖;

接下来发生了依赖值的变化,由于之前依赖收集过,所以,effect的函数自然会重新执行一次,其实这已经实现了响应式;即数据变化了,重新执行了函数;

但由于条件表达式的存在,我们再次运行函数时,有可能我们依赖的值不再是之前的值,也就是说,之前的依赖值我们在这次运行中用不到了,如果这个用不到的依赖值变化时,我们是不需要重新运行函数的,这样可以提高性能;、

由于这次执行函数时,我们可以清楚地知道要依赖哪些值,并且也会进行依赖收集,所以,在执行函数之前,将之前的依赖收集全部清空,在这次运行函数时,我们重新收集,并且,这次收集才是正确的,只有这次计算中的依赖项变化,我们才运行函数,之前的依赖项变化我们不再理会;

接下来,只有上一次运行的函数中用到的依赖项变化了,我传递才找到activeEffect,并重新执行函数;

这一次执行函数,会执行新的代码,确定新的依赖项;

同样,我们在运行函数之前,把之前的依赖项清空;也就是将所以属性对应的set中的当前effect删除掉,这们,当属性更新时,我们当前effect是不会重新运行fn的;

<body>
  <script src="./reactivity.global.js"></script>
  <div id="app"></div>
  <script>
    const { effect, reactive } = VueReactivity;
    const state = reactive({ flag: true, name: 'jw', age: 30 })
    effect(() => {
      console.log('render')
      document.body.innerHTML = state.flag ? state.name : state.age
    });
    setTimeout(() => {
      state.flag = false;
      setTimeout(() => {
        console.log('修改name,原则上不更新')
        state.name = 'zf'
      }, 1000);
    }, 1000)
  </script>
</body>
修改src/effect.ts代码
添加一个方法
function cleanupEffect(effect) {
  const { deps } = effect;
  for (let i = 0; i < deps.length; i++) {
    deps[i].delete(effect); // 解除effect,重新依赖收集
  }
  effect.deps.length = 0;
}
class ReactiveEffect {
  public deps = [];
  public parent = null;
  public active = true
  constructor(public fn) { }
  run() {
    if (!this.active) {
      this.fn();
    }
    try {
      this.parent = activeEffect;
      activeEffect = this;
      ==//== ==这里我们需要在执行用户函数之前将之前收集的内容清空==
      ==cleanupEffect====(====this====)==
      return this.fn();
    } finally {
      activeEffect = this.parent;
      this.parent = null;
    }
  }
}
修改trigger方法
export function trigger(target, type, key, value, oldValue) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return; // 触发的值不在模板中使用
  let effects = depsMap.get(key); // 找到了属性对应的effect
  ==if== ==(====effects====)== =={==
    ==effects== ===== ==new== ==Set====(====effects====);==
    ==effects====.====forEach====(====effect== ===>== =={==
      ==if== ==(====effect== ==!==== ==activeEffect====)== ==effect====.====run====();==
    ==})==
  ==}==
}

本站总访问 — 次