解读vue3源码-响应式篇3 effect副作用函数

news2024/11/13 12:50:55

提示:看到我 请让我滚去学习

文章目录

  • 前言
  • effect问题拓展
    • 分支切换与 cleanup
    • 嵌套的 effect 与 effect 栈
    • 解决在副作用函数中同时读取和操作同一属性时无限循环
  • effect函数实现
    • computed-api 实现图解
    • 在这里插入图片描述
  • 总结


前言

什么是副作用函数?

在 Vue 3 中,副作用函数(Effect Function)通常指的是那些依赖于响应式数据并在数据变化时重新执行的函数。Vue 3 引入了 ref 和 reactive API 来创建响应式数据,以及 effect 函数来观察和处理这些数据的变化。

当一个函数被标记为副作用函数时,它会被 Vue 的响应式系统跟踪。这意味着每当这个函数依赖的响应式数据发生变化时,Vue 会自动重新执行这个函数,从而触发 UI 的更新或其他相关操作。

例如:
import { ref, effect } from 'vue';
const count = ref(0);
// 定义一个副作用函数
effect(() => {
  console.log('Count changed:', count.value);
});
// 更改count的值,副作用函数将被重新执行
count.value++;

effect问题拓展

以我们的精简版本的watchEffect 函数做模本例子,我们将对其进行拓展

  function watchEffect (effect) {
    activeEffect = effect
    effect()
  }

全局targetMap存储结构:
在这里插入图片描述

分支切换与 cleanup

我们就简单的watchEffect ,会有很多问题,比如下面的示例:

  const data = { ok: true, a: 111, b: 222 }
  const obj = reactive(data)
  watchEffect(function effectFn () {
    console.log(obj.ok ? obj.a : obj.b)
  })
  //111
  obj.ok = false  //222
  obj.a = 3 //222

我们使用一个三元表达式,第一次初始化watchEffect函数,因为obj.ok 为true,所以访问的是obj.ok和obj.a,那么全局targetMap如下:
在这里插入图片描述
当我们修改obj.ok的值后,重新触发watchEffect函数,再次访问内部依赖,因为obj.ok值改变,所以访问的属性变成了obj.ok和obj.b,这样obj.b就会收集到targetMap中,全局targetMap如下:
在这里插入图片描述
这样就有了问题,我们最新的effect其实是ok和b的依赖,但是依赖表中存储了ok、a、b三个属性,所以当我们访问a时,还是会触发watchEffect重新执行。
解决这个问题的思路很简单,每次副作用函数执行时,我们可以先把它从所有与之关联的依赖集合中删除,要将一个副作用函数从所有与之关联的依赖集合中移除,就需要明确知道哪些依赖集合中包含它。所以我们在响应式数据访问收集副作用函数时,需要给副作用函数添加一个dep[]属性,然后将当前触发依赖收集的属性push到此dep中。
在这里插入图片描述
那我们来看vue3源码实现:
其实过程就是:实现双向记忆(运行effect时,找到所有绑定这个effect的属性,删除所有属性上的此effect)
1.首先ReactiveEffect类中定义了属性_depsLength,这个属性是为了记录当前收集的dep是同一个effect中的第几个响应式属性
在这里插入图片描述
在run方法中每次执行副作用函数前都会调用preCleanupEffect方法,这个方法就是重置当前effect内部的指针,_trackId用于记录当前effect实例是第几次运行,_depsLength表示当前是第几个dep收集,每次重走effect方法,都会重置_depsLength为0,表示从第1个开始收集
在这里插入图片描述
在这里插入图片描述
而在track函数执行收集依赖时,我们看下effect是怎么记录dep的
在这里插入图片描述
这里有一个简单的diff算法,我们知道_depsLength用来记录当前dep是effect方法中第几个记录的,比如我在上述示例:

 watchEffect(function effectFn () {
    console.log(obj.ok ? obj.a : obj.b)
  })

当obj.ok为true时,第一次进入watchEffect,effectFn副作用函数中监听的值为ok和a,那么effectFn的deps[]中就是[ok,a],当我们修改ok的值,就会触发effectFn执行,effectFn中再次访问值为ok和b,这时候会按照顺序将新值和旧值按顺序对比,第一个dep不变是ok,然后用b替换a。

  const stop=effect(function effectFn111 () {
        console.log('11111', obj.a, obj.a)
      })

此处_trackId用于记录当前effect副作用函数运行次数,if (dep.get(effect2) !== effect2._trackId) {}。可以过滤到上面这种一个函数中多次使用一个变量的情况

此时还会有一个新问题,那么如果第一次副作用函数绑定了2个属性上,但是第二次运行时只需要绑定1个了,例如

 watchEffect(function effectFn () {
    console.log(obj.ok ? obj.a : null)
  })

那么在执行完替代后需要根据新列表数量,删除多余列表长度,实现代码如下:
在这里插入图片描述
vue3.0是使用set存储effect依赖,但是set是无序的,而这也是为什么改成有序的map存储effects的原因之一。

嵌套的 effect 与 effect 栈

我们vue3是可以支持组件嵌套的,所以本质effect也是应该要支持嵌套,例如:

effect(function effectFn1() {
	effect(function effectFn2() { /* ... */ })
    console.log(pro.a)
 })

但是这样就会有问题,像上面这样写,pro.a绑定的副作用函数是effectFn2并非effectFn1,这是因为我们用全局变量 activeEffect 来存储通过 effect 函数注册的副作用函数,这意味着同一时刻 activeEffect 所存储的副作用函数只能有一个。当副作用函数发生嵌套时,内层副作用函数的执行会覆盖 activeEffect 的值,并且永远不会恢复到原来的值。这时如果再有响应式数据进行依赖收集,即使这个响应式数据是在外层副作用函数中读取的,它们收集到的副作用函数也都会是内层副作用函数,这就是问题所在。
而我们的代码中只需要在effect运行之初记录上次的effect,在副作用函数执行完毕时,恢复上次的effect就行。
在这里插入图片描述

解决在副作用函数中同时读取和操作同一属性时无限循环

  const data = { ok: true, a: 111, b: 222 }
  const obj = reactive(data)
  watchEffect(function effectFn111 () {
    console.log('11111', obj.a++)
  })
  obj.a = 666

这种在副作用函数中既读取又操作一个属性会爆栈,这是因为首先读取 obj.a的值,这会触发 track 操作,将当前副作用函数收集到“桶”中,接着将其加 1 后再赋值给 obj.a,此时会触发 trigger 操作,即把“桶”中的副作用函数取出并执行。但问题是该副作用函数正在执行中,还没有执行完毕,就要开始下一次的执行。这样会导致无限递归地调用自己,于是就产生了栈溢出。
解决方案,在trigger触发时判断要执行的副作用函数是否正在执行,正在执行就跳过。
vue3代码通过_running变量控制在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

effect函数实现

effect函数原本在vue3.0中是没有暴露的,vue官方文档并没有相关说明,那么我们看看它是怎么实现的。
在这里插入图片描述

以上就是effect函数的实现,接收两个参数,一个是运行时的副作用函数,另一个options,主要作用如下:
在这里插入图片描述
这个函数就是实例化并抛出了ReactiveEffect类,并且根据参数运行一次对象的run方法,那我们进入ReactiveEffect中看看ReactiveEffect是怎么实现的
在这里插入图片描述
ReactiveEffect主要方法是包含
初始化方法,
get diety()和set dirty()属性访问器(主要用于记录computed-api是否需要重新计算),
run方法,
stop方法:停止effect,watch函数返回值即stop函数,执行停止监听。

我们先看看构造方法中做了什么
在这里插入图片描述
fn:即我们传入的副作用函数,我们将其赋值给class内部属性,可以预见其在我们的run函数触发的时候执行,
shouldSchedule:为调度器,默认为false,如果外部传入,我们用shouldSchedule来执行副作用函数
active:标识创建的effect是响应式的,若为false就不会赋值activeEffect关联属性
dirtyLevel:计算属性使用,记录是否是脏数据
trackId:用于记录当前effect执行了几次
runnings:是否正在执行
deps:当前副作用函数绑定的dep,属性和effect做双向记忆
deplength:用来标识当前属性是同一个effect中收集第几个依赖

我们看run方法,也就是执行方法。这个方法其实主要就是做了2件事:
1.将这个effect副作用函数赋值给全局的activeEffect,在响应式数据触发读取的时候,和数据访问属性进行绑定
2.执行一次副作用函数
在这里插入图片描述

至此总体过一遍响应式流程:
例子:effect(function fn(){ proxya.name })
1.effect函数运行创建ReactiveEffect对象,运行其run方法。
2.run方法将ReactiveEffect赋值给全局的activeEffect对对象,并且运行执行方法fn,
3.fn方法中访问proxya.name,触发name属性拦截,触发依赖收集将activeEffect(即fn)和proxya.name绑定。
4.effect运行完毕全局activeEffect赋值为上个堆栈的activeEffect对象(此处为undefined)
5.修改proxya.name值,触发set拦截,触发依赖执行proxya.name绑定的effect(即3中绑定的ReactiveEffect—fn方法)

computed-api 实现图解

在这里插入图片描述

总结

至此我们就能大体了解响应式数据的实现,而对于一些衍生的api,如watch、watchEffecte、computed等我们都可自行阅读源码,其本质都是使用RactiveEffect类实现。

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

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

相关文章

使用 Python 进行时间序列特征提取,从理论到实践

以下是提取时间序列分析特征时需要了解的所有内容 时间序列是一种特殊的动物。欢迎来到雲闪世界。 当我开始我的机器学习职业生涯时,我这样做是因为我喜欢物理(开始机器学习的奇怪原因),并且从物理学中我了解到我也非常喜欢编码…

构建技术故障应急响应机制:开发团队的全面策略

构建技术故障应急响应机制:开发团队的全面策略 前言一、快速响应与问题定位实时监控与预警日志管理与分析分布式追踪紧急响应机制 二、预案与备份机制应急预案制定应急演练数据备份与恢复冗余部署与负载均衡 三、事后总结与持续改进全面复盘成果转化与实施持续改进机…

Prompt + 工作流组件 = AI智能体:开启智能化新时代

在这个信息爆炸的时代,人工智能(AI)正以前所未有的速度改变着我们的生活和工作方式。随着技术的不断进步,AI的应用场景也在不断扩展。而在这一过程中,Prompt和工作流组件的结合,正是构建AI智能体的关键所在…

Android中apk安装过程源码解析

本文中使用的Android源码基于Android 14 1 三方应用安装apk调用方法 public void installApk() {Intent intent new Intent(Intent.ACTION_VIEW);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);/** 自Android N开始,是通过FileProvider共享相关文件&#xff0…

NTU发布即插即用的嵌入式SLAM新SOTA!

导读: 当前的SLAM技术正逐渐向轻量化和高效化发展,在保证定位精度的同时,研究者们越来越倾向于简化冗余的框架,以在效率和性能之间找到最佳平衡点。 一个优秀的SLAM系统不仅需要提供精确的定位数据,还必须能够在不同平…

【PyQt】切换界面的实现

前言一、介绍二、代码2.1 QStackedWidget2.1.1 代码2.1.2 运行结果展示 2.2 QTabWidget2.2.1 代码2.2.2 运行结果展示 2.3 QDockWidget2.3.1 代码2.3.2 运行结果展示 PULSE结果 总结 前言 实现几个界面的切换展示的效果。 一、介绍 在 PyQt 中实现一个主界面包含其他子界面的…

#mark# ViteSvgIconsPlugin svgoOptions选项

svgoOptions 设置为false时 , 矢量svg图标 stroke线条不正常 找了半天 哪影响的 , 线条异常图如下 svgoOptions 改为true后 ,显示正常了

并查集优化策略及其正确性证明:基于路径压缩与按秩合并

并查集优化策略及其正确性证明:基于路径压缩与按秩合并 前言优化策略算法伪代码C语言实现归纳法证明基础情况归纳步骤结论前言 引理:对于所有的结点x, 有 x.rank≤x.p.rank, 如 果x≠x.p, 则此式是严格不等 式。x.rank 的初始值为0,并且随时间而增加,直到x≠x.p; 从此以后,…

安卓13 背光反向 亮度反向 android13 backlight reverse

总纲 android13 rom 开发总纲说明 目录 1.前言 2.问题分析 3.代码分析 4.代码修改 5.彩蛋 1.前言 有些设备,在调整背光的时候,会发现,背光调大,显示亮度反而变暗,背光调小,亮度变亮。这是由于PWM背光本身并没有一个标准去决定怎么样算是高亮度,怎么算是低亮度。因…

C语言-02 算法-程序的灵魂

算法-程序的灵魂 1、程序=算法数据结构2、什么是算法3、简单的算法举例4、算法的特性5、怎样表示一个算法5.1 用自然语言表示算法5.2 用流程图表示算法5.3 三种基本结构和改进的流程图5.4 用N-S流程图表示算法5.5 用伪代码表示算法5.6 用计算机语言表示算法 6、结构…

秋冬春夏,纪念在CSDN的第365天

目录 时光 收获 工作 生活 憧憬 时光 再次收到创作纪念日的消息时,已在CSDN创作和度过了一年的时光。创作,成了自己的第二工作空间,成为了日常的一种习惯。 每当看到第1篇文章的提醒消息,都会想起当时创作的初衷和情景。是一…

学了PMP能给我们带来什么变化?

一、PMP是什么 PMP(Project Management Professional)是一个在项目管理领域具有高级认证的资格。该认证由PMI在全球200多个国家和地区广泛推广,被认为是项目管理领域内最具含金量的认证之一。 持有PMP证书的项目经理不仅可以提升其项目管理能力,同时也…

LLaMA代码笔记 --基于lit-llama

代码来自:lit-llama modelscope模型下载 :llama-7b 下载后的模型需要转换为lit-llama使用的格式,详见 howto 文件夹下的 download_weights.md 文中代码为了方便说明,删减了一些内容,详细代码请查看源码。 generate …

磁场强度H和磁感应强度B,磁化强度M和磁极化强度J

磁场强度H、磁感应强度B、磁化强度M和磁极化强度J是四个非常重要的磁学基本概念,他们之间关联但有时又很容易混淆。分清这四个概念对于磁材行业从业者是非常重要的,今天我们就为大家细说一下它们的概念和关系。 磁场强度H 磁场强度H其实是一个没有实际…

自动化巨头施耐德电气,部分业务被其供应商收购:之前还收购过霍尼韦尔

导语 大家好,我是社长,老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》 供应商逆袭:小鱼吃大鱼的商业奇迹 英国电气行业掀起一阵惊涛骇浪。斯塔福德郡的中型企业Goodfish Group竟然收购了全球巨头施耐德电气…

AppInventor2 现已全面支持安卓14!

//重磅升级:支持安卓14// MIT于2024/08/19升级Android SDK至34(安卓14),因为在 2024 年 8 月 31 日之后,在 Google Play 商店中添加或更新应用时必须执行此更新。 国内估计也会跟进,因此使用最新版本的 s…

CleanMyMac如何帮助用户清空DNS缓存,Mac清除dns缓存命令

什么是DNS缓存?这个缓存有什么危害?相信大家平时使用浏览器时,有时候会遇到一个很奇怪的问题,就是Mac打开许多网站如百度网站,都是可以访问的,但是在打开某个特定网站时,却发现浏览器提示检测不…

网上商城|基于SprinBoot+vue的分布式架构网上商城系统(源码+数据库+文档)

分布式架构网上商城系统 目录 基于SprinBootvue的分布式架构网上商城系统 一、前言 二、系统设计 三、系统功能设计 5.1系统功能模块 5.2管理员功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取: 博主介绍…

Halcon根据灰度特征值选择区域

Halcon根据灰度特征值选择区域 与select_shape算子类似,灰度值图像也可以快捷地根据特征值选择符合设定条件的区域。select_gray算子用于实现这一功能,该算子能接受一组区域作为输入,然后根据选定的特征计算其是否满足特定的条件。当所有区域…

网络安全 DVWA通关指南 DVWA File Upload(文件上传)

DVWA File Upload(文件上传) 文章目录 DVWA File Upload(文件上传)修复建议 LowMediumHighImpossible 修复建议 1、使用白名单限制可以上传的文件扩展名 2、注意0x00截断攻击(PHP更新到最新版本) 3、对上传…