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

news2025/5/15 9:37:58

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 执行’


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2375985.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AAAI-2025 | 电子科大类比推理助力精准识别!SPAR:基于自提示类比推理的无人机目标探测技术

作者&#xff1a; Nianxin Li, Mao Ye, Lihua Zhou, Song Tang, Yan Gan, Zizhuo Liang, Xiatian Zhu 单位&#xff1a;电子科技大学计算机科学与工程学院&#xff0c;上海理工大学机器智能研究所&#xff0c;重庆大学计算机学院&#xff0c;谢菲尔德大学&#xff0c;萨里大学…

速查 Linux 常用指令 II

目录 一、网络管理命令1. 查看和配置网络设备&#xff1a;ifconfig1&#xff09;重启网络命令2&#xff09;重启网卡命令 2. 查看与设置路由&#xff1a;route3. 追踪网络路由&#xff1a;traceroute4. 查看端口信息和使用情况1&#xff09;netstat 命令2&#xff09;lsof 命令…

IIS服务器URL重写配置完整教程

1.下载URL Rewrite Module 2.1 https://www.iis.net/downloads/microsoft/url-rewrite https://download.microsoft.com/download/1/2/8/128E2E22-C1B9-44A4-BE2A-5859ED1D4592/rewrite_amd64_zh-CN.msi 2.安装

注解和 XML 两种方式有什么区别?

注解和 XML 是两种常见的配置方式&#xff08;尤其在 Java 开发中&#xff0c;如 Spring 框架&#xff09;&#xff0c;它们的主要区别体现在配置方式、代码耦合性、可读性、维护性等方面。以下是两者的对比&#xff1a; 1. 配置方式 注解&#xff08;Annotation&#xff09; 在…

高速系统设计实例设计分析二

6.6 仿真约束的生成和实施 进行到这一步&#xff0c;我们已经完成了对实例进行仿真的所有条件的设置&#xff0c;包括对板子的设计要求分析和预布局处理。虽然从技术上讲&#xff0c;我们可以开始进行仿真分析并生成设计的约束&#xff0c;但是根据作者的工作经验&#xff0c;…

【MySQL】变更缓冲区:作用、主要配置以及如何查看

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…

C2S-Scale:Cell2Sentence v2

目前的单细胞基础模型&#xff08;scFMs&#xff09;在可扩展性、跨多种任务的灵活性以及整合文本信息的能力方面仍然有限。基于Cell2Sentence&#xff08;C2S&#xff09;框架展开工作&#xff0c;该框架将单细胞RNA测序&#xff08;scRNA-seq&#xff09;图谱表示为文本形式的…

技术伦理双轨认证如何重构AI工程师能力评估体系——基于AAIA框架的技术解析与行业实证研究

引言&#xff1a;AI工程师能力评估的范式转型 2025年全球人工智能产业呈现出两大特征&#xff1a;技术迭代加速与监管框架完善。据Gartner数据显示&#xff0c;全球75%的企业在AI项目部署中遭遇技术伦理混合型难题&#xff0c;传统单维度技术认证体系已无法满足产业需求。本文…

ubuntu20.04系统搭建k8s1.28集群-docker作为容器运行时

ubuntu系统搭建 ubuntu-22.04.5-desktop-amd64.iso映像文件--->实际却是20.4focal版本。 【安装过程没有特别指出的默认回车下一步】 【用户和密码设置】 【网络连接】 【在vmware上安装的话&#xff0c;网络配置如下】【在vm里配置选择nat或者桥接即可】 【国内源配置】&…

【Alist+RaiDrive挂载网盘到本地磁盘】

1.安装准备 安装RaiDrive RaiDrive - 像 USB 驱动器一样安装云存储 安装alist 安装方式请查看官网: AList文档 2.启动Alist(docker) docker官网 Install | Docker EngineDocker Desktop | Docker Docs 运行容器 docker run -d --restartalways -v /home/alist:/opt/alist/…

vue实现进度条带指针

效果最终 function calculatePointerPosition(value) {if (value < 2.6) return 12.5; // 非常差位置if (value < 5.1) return 37.5; // 较差位置if (value < 7.1) return 62.5; // 良好位置return 90; // 非常满意位置 }function getStatusText(value) {if (valu…

Kafka Go客户端--Sarama

Kafka Go客户端 在Go中里面有三个比较有名气的Go客户端。 Sarama:用户数量最多&#xff0c;早期这个项目是在Shopify下面&#xff0c;现在挪到了IBM下。segmentio/kafka-go:没啥大的缺点。confluent-kafka-go&#xff1a;需要启用cgo,跨平台问题比较多&#xff0c;交叉编译也…

RustDesk:开源电脑远程控制软件

RustDesk&#xff1a;开源电脑远程控制软件 RustDesk&#xff1a;开源电脑远程控制软件一、RustDesk 简介二、下载教程2.1 桌面版下载2.2 Android 版下载 三、安装教程3.1 桌面版安装 四、功能讲解4.1 远程控制4.2 文件传输4.3 安全可靠4.4 自定义服务器 五、RustDesk技术架构解…

[操作系统] 策略模式进行日志模块设计

文章目录 [toc]一、什么是设计模式&#xff1f;二、日志系统的基本构成三、策略模式在日志系统中的落地实现✦ 1. 策略基类 LogStrategy✦ 2. 具体策略类▸ 控制台输出&#xff1a;ConsoleLogStrategy▸ 文件输出&#xff1a;FileLogStrategy 四、日志等级枚举与转换函数五、日…

MoonBit正式入驻GitCode!AI时代的编程语言新星,开启高性能开发新纪元

在AI与编程语言深度交融的今天&#xff0c;开发者们正见证一场技术生产力的革命。由IDEA研究院基础软件中心倾力打造的MoonBit&#xff08;月兔&#xff09;编程语言&#xff0c;自2023年横空出世以来&#xff0c;凭借高性能、低延迟、轻量化的特性&#xff0c;迅速成为全球开发…

关于vue学习的经常性错误

目录 常见问题&#xff1a; 1关于引用本地下载es6模块文件&#xff0c;报404错误 2 使用createApp函数后没有调用mount函数挂载到浏览器 3 在mount函数中&#xff0c;忘记引用插值表达式所在标签的定位符如 标签选择器&#xff0c;类选择器等 4在直接使用Vue3函数时&#…

AtCoder Beginner Contest 403

再来一场atCoder&#xff0c;这一场简直血虐&#xff0c;让你回忆起了审题的重要性 A - Odd Position Sum 思路&#xff1a;题意很简单&#xff0c;求一个数组奇数位上数字和。很简单的问题&#xff0c;但你如果不仔细审题&#xff0c;就会浪费大量的时间 /* Author Owen_Q…

关于 Golang GC 机制的一些细节:什么是根对象?GC 机制的触发时机?

文章目录 关于 Golang GC 机制的一些细节&#xff1a;什么是根对象&#xff1f;GC 机制的触发时机&#xff1f;简要回顾 Golang GC 三色标记法的工作流程什么是根对象&#xff1f;GC 的触发时机&#xff1f; 关于 Golang GC 机制的一些细节&#xff1a;什么是根对象&#xff1f…

Python笔记:c++内嵌python,c++主窗口如何传递给脚本中的QDialog,使用的是pybind11

1. 问题描述 用的是python 3.8.20, qt版本使用的是5.15.2, PySide的版本是5.15.2, pybind11的版本为2.13.6 网上说在python脚本中直接用PySide2自带的QWinWidget&#xff0c;如from PySide2.QtWinExtras import QWinWidget&#xff0c;但我用的版本中说没有QWinWidget&#x…

C++效率掌握之STL库:map set底层剖析及迭代器万字详解

文章目录 1.map、set的基本结构2.map、set模拟实现2.1 初步定义2.2 仿函数实现2.3 Find功能实现2.4 迭代器初步功能实现2.4.1 运算符重载2.4.2 --运算符重载2.4.3 *运算符重载2.4.4 ->运算符重载2.4.5 !运算符重载2.4.6 begin()2.4.7 end() 2.5 迭代器进阶功能实现2.5.1 set…