【Vue3源码】第二章 effect功能的完善补充

【Vue3源码】第二章 effect功能的完善补充

前言

上一章节我们实现了effect函数的功能stop和onstop,这次来优化下stop功能。

优化stop功能

之前我们的单元测试中,stop已经可以成功停止了响应式更新(清空了收集到的dep依赖)

stop功能其实还存在很大的问题,就是不能停止对象中的++操作

为什么呢?

看个例子

let user = {
    
     age : 10 }
 user.age++
//++是一个语法糖,他在js中的表现为
//user.age = user.age + 1

看了例子我们就可以明白为什么stop功能会存在漏洞了,因为user.age = user.age + 1 这句代码在响应式中user.age + 1会先触发get捕获器然后 +1触发set捕获器

那么get捕获器又会重新收集依赖,我们之前stop清空的依赖就又回来了导致无法stop响应式更新这个漏洞出现

怎么解决这个问题?

我们可以从run方法和track函数切入,只要判断调用run方法track就去收集依赖,如果是stop调用的run方法,track函数就不要去收集依赖即可。

1.单元测试代码

进入effect.spec.ts文件

扫描二维码关注公众号,回复: 14924460 查看本文章

还是使用之前的stop单元测试代码即可

我们把obj.prop = 3 改为 obj.prop++

 it("stop",() => {
    
    
    let dummy;
    const obj = reactive({
    
    prop:1})
    const runner = effect(() => {
    
    
      dummy = obj.prop
    })
    obj.prop = 2
    expect(dummy).toBe(2)
    stop(runner)
    // 修改
    // obj.prop = 3
    obj.prop++
    expect(dummy).toBe(2)

    //stopped effect should still be manually callable
    runner()
    expect(dummy).toBe(3)

  })

2.优化run方法

进入effect.ts文件

我们再通过新增一个全局变量shouldTrack,作为开关,去有效的控制track进行依赖收集

//新增
let shouldTrack;

class ReactiveEffect {
    
    
  private _fn;
  active = true;
  deps = [];
  onStop?: () => void;
  constructor(fn, public scheduler?) {
    
    
    this._fn = fn;
    this.scheduler = scheduler;
  }
  //修改
  run() {
    
    
    // 3.因为默认shouldTrack= false
    // 4.调用了stop的时候直接执行了this._fn()
    // 5.但是shouldTrack是关上的
    if (!this.active) {
    
    
      return this._fn()
    }
    // 1.run的时候才会开启开关
    shouldTrack = true;
    activeEffect = this;
    const result = this._fn();
    // 2.reset,生成ReactiveEffect类时,我们都会默认调用 _effect.run()方法,所以默认执行完this._fn()后都会重置 shouldTrack = false

    shouldTrack = false;

    return result
  }
  stop() {
    
    
    if (this.active) {
    
    

      clearupEffect(this);
      if (this.onStop) {
    
    
        this.onStop()
      }
      this.active = false
    }
  }
}

调用run()方法的时候会去开启shouldTrack开关,因为生成ReactiveEffect类时我们都会默认调用 _effect.run()方法,所以默认的 this._fn()执行完后都会使shouldTrack 重置为 shouldTrack = false

在这里插入图片描述

那么就出现以下两种情况:

正常调用effect.run() 和 stop调用 effect.run()

他们通过shouldTrack这个开关就可以识别,所以我们可以通过shouldTrack全局变量去判断track是否该触发依赖收集

3.优化track函数

vue3源码就是通过shouldTrackactiveEffect两个开关去控制track函数执行,来达到可以控制的依赖收集功能

//依赖收集
export function track(target, key) {
    
    
  // 6.如果是stop调用那么在触发track时,因为shouldTrack是false,所以track就不能执行了
  // 7.如果是run()调用那么在触发track时,因为shouldTrack是true,所以可以执行track逻辑,等track结束,才把shouldTrack = false
  if (activeEffect && shouldTrack) {
    
    
    let depsMap = targetMap.get(target);
    if (!depsMap) {
    
    
      depsMap = new Map();
      targetMap.set(target, depsMap);
    }

    let dep = depsMap.get(key);
    if (!dep) {
    
    
      dep = new Set();
      depsMap.set(key, dep);
    }
    dep.add(activeEffect);
    //浅拷贝反向收集到dep
    activeEffect.deps.push(dep);
  }
}

4.优化clearupEffect函数

用for代替了forEach提高性能,如果清空了依赖我们就把deps的length也清空,最大程度的优化性能

function clearupEffect(effect) {
    
    
  const {
    
     deps } = effect
  if (deps.length) {
    
    
    for (let i = 0; i < deps.length; i++) {
    
    
      //因为是浅拷贝收集到的dep,所以这里删掉对应的dep就没有了,没有dep(二级分类)自然就无法触发run方法!
      deps[i].delete(effect)
    }
    deps.length = 0
  }
}

5.代码运行流程图

上面就基本把stop的漏洞修复了,我们来看下run方法运行和stop方法运行时的区别

在这里插入图片描述
通过所有测试!
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_68324632/article/details/129115605