Vue.js---嵌套的effect与effect栈

发布于:2025-05-14 ⋅ 阅读:(18) ⋅ 点赞:(0)

4.3嵌套的effect与effect栈

1、嵌套的effect

effect是可以发生嵌套的

01 effect(function effectFn1() {
02   effect(function effectFn2() { /* ... */ })
03   /* ... */
04 })

有这么一段代码:

01 // 原始数据
02 const data = { foo: true, bar: true }
03 // 代理对象
04 const obj = new Proxy(data, { /* ... */ })
05
06 // 全局变量
07 let temp1, temp2
08
09 // effectFn1 嵌套了 effectFn2
10 effect(function effectFn1() {
11   console.log('effectFn1 执行')
12
13   effect(function effectFn2() {
14     console.log('effectFn2 执行')
15     // 在 effectFn2 中读取 obj.bar 属性
16     temp2 = obj.bar
17   })
18   // 在 effectFn1 中读取 obj.foo 属性
19   temp1 = obj.foo
20 })

我们希望副作用函数与对象属性之间的关系如下:

01 data
02   └── foo
03     └── effectFn1
04   └── bar
05     └── effectFn2

但是当我们发现我们修改foo的值出来的却是:

01 'effectFn1 执行'
02 'effectFn2 执行'
03 'effectFn2 执行'

一共打印三次,前两次分别是副作用函数effectFn1 与 effectFn2 初始执行的打印结果,到这一步是正常的,问题出在第三行打印。我们修改了字段 obj.foo 的值,发现 effectFn1 并没有重新执行,反而使得 effectFn2 重新执行了,这显然不符合预期。

问题出现在哪里呢?

01 // 用一个全局变量存储当前激活的 effect 函数
02 let activeEffect
03 function effect(fn) {
04   const effectFn = () => {
05     cleanup(effectFn)
06     // 当调用 effect 注册副作用函数时,将副作用函数赋值给 activeEffect
07     activeEffect = effectFn
08     fn()
09   }
10   // activeEffect.deps 用来存储所有与该副作用函数相关的依赖集合
11   effectFn.deps = []
12   // 执行副作用函数
13   effectFn()
14 }

我们用全局变量 activeEffect 来存储通过 effect函数注册的副作用函数,这意味着同一时刻activeEffect 所存储的副作用函数只能有一个。当副作用函数发生嵌套时,内层副作用函数的执行会覆盖 activeEffect 的值,并且永远不会恢复到原来的值,那么foo应该放入的是effect1也会被effect2所覆盖。

2、effect栈

如何解决???effect栈

在副作用函数执行时,将当前副作 用函数压入栈中,待副作用函数执行完毕后将其从栈中弹出,并始终让 activeEffect 指向栈顶的副作用函数。这样就能做到一个响应式数据只会收集直接读取其值的副作用函数,而不会出现互相影响的情况。

修改代码:

  let activeEffect;
  // effect栈
  const effectStack = [];
  function effect (fn) {
    const effectFn = () => {
    // 调用clearup函数完成清除工作
    clearUp(effectFn);
    activeEffect = effectFn; 
    // 在调用副作用函数之前将副作用函数压入栈中
    effectStack.push(effectFn);
    fn();
      // 执行完之后抛出,把activeEffect还原成原来的值
    effectStack.pop();
    //effectStack[effectStack.length - 1] 用于获取栈顶的副作用函数,并将其设置为 activeEffect。
    activeEffect = effectStack[effectStack.length - 1];
    }
    // deps用来存储所有与该副作用函数相关联的依赖集合
    effectFn.deps = [];
    // 执行副作用函数
    effectFn();
  }

完整代码:

<script setup>
  let activeEffect;
  // effect栈
  const effectStack = [];
  function effect (fn) {
    const effectFn = () => {
    // 调用clearup函数完成清除工作
    clearUp(effectFn);
    activeEffect = effectFn; 
    // 在调用副作用函数之前将副作用函数压入栈中
    effectStack.push(effectFn);
    fn();
      // 执行完之后抛出,把activeEffect还原成原来的值
    effectStack.pop();
    activeEffect = effectStack[effectStack.length - 1];
    }
    // deps用来存储所有与该副作用函数相关联的依赖集合
    effectFn.deps = [];
    // 执行副作用函数
    effectFn();
  }
  
  const bucket = new WeakMap();
  const data = { text: 'world' }; // 确保所有属性都已定义
  const obj = new Proxy(data, {
    get(target, key){
      track(target , key);
      return target[key];
    },
    set(target, key, newVal){
      target[key] = newVal;
      trigger(target , key , newVal);
    }
  });
  // 追踪变化
  function track(target , key){
    if(!activeEffect){
        return;
      }
      // 根据tartget取来的depsMap,它是一个map类型
      let depsMap = bucket.get(target);
      // 如果不存在
      if(!depsMap){
        // 创建一个
        bucket.set(target, (depsMap = new Map()));
      }
      // 根据key取来的deps,它是一个set类型
      let deps = depsMap.get(key);
      // 如果不存在
      if(!deps){
        // 创建一个
        depsMap.set(key, (deps = new Set()));
      }
      deps.add(activeEffect); // 添加当前活跃的副作用函数
      activeEffect.deps.push(deps);
  }
// 触发变化
  function trigger(target, key, newVal) {
    const depsMap = bucket.get(target);
    if (!depsMap) {
      return;
    }
    const effects = depsMap.get(key);
    const effectsToRun = new Set(effects);
    effectsToRun.forEach(effectFn => effectFn());
    // effects && effects.forEach(fn => fn()); // 只触发与键相关的副作用函数
  }
  // 清除函数
   function clearUp (effectFn){
     // 遍历然后进行删除
     for(let i = 0 ; i < effectFn.deps.length ; i++){
       const deps = effectFn.deps[i];
       // 移除
       deps.delete(effectFn);
     }
     // 最后重置effectFn.deps数组
     effectFn.deps.length = 0;
     
   }
  effect(() => {
    console.log('run');
    document.body.innerText = obj.text;
  });
  setTimeout(() => {
    obj.text = "hello vue3"; // 修改已定义的属性以触发依赖
  }, 1000);
</script>

在这里插入图片描述

由图所示:那么外层的副作用函数就会压到栈底,内层就在栈顶

[effect2,effect1]=> [effect1]=>[]:修改foo输出

01 'effectFn1 执行'
02 'effectFn2 执行'
03 'effectFn2 执行'
04 'effectFn1 执行'

与键相关的副作用函数
}
// 清除函数
function clearUp (effectFn){
// 遍历然后进行删除
for(let i = 0 ; i < effectFn.deps.length ; i++){
const deps = effectFn.deps[i];
// 移除
deps.delete(effectFn);
}
// 最后重置effectFn.deps数组
effectFn.deps.length = 0;

}
effect(() => {
console.log(‘run’);
document.body.innerText = obj.text;
});
setTimeout(() => {
obj.text = “hello vue3”; // 修改已定义的属性以触发依赖
}, 1000);


[外链图片转存中...(img-84C5fgLt-1747235972579)]

由图所示:那么外层的副作用函数就会压到栈底,内层就在栈顶

[effect2,effect1]=> [effect1]=>[]:修改foo输出

01 ‘effectFn1 执行’
02 ‘effectFn2 执行’
03 ‘effectFn2 执行’
04 ‘effectFn1 执行’



网站公告

今日签到

点亮在社区的每一天
去签到