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

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

文章目录

  • 前言
  • 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 = effecteffect()}

全局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)})//111obj.ok = false  //222obj.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.mzph.cn/pingmian/52361.shtml

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

相关文章

SCYC 56901传感器SCYC 56901模块面价

SCYC 56901传感器SCYC 56901模块面价 SCYC 56901传感器SCYC 56901模块面价 SCYC 56901传感器SCYC 56901模块面价 SCYC 56901传感器SCYC 56901模块引脚线 SCYC 56901传感器SCYC 56901模块说明书 SCYC 56901传感器SCYC 56901模块电路图 SCYC 56901温度传感器是早开发&#…

iPhone 手机使用技巧:iPhone 数据恢复软件

无论是由于意外删除、系统崩溃还是软件更新,丢失 iPhone 上的数据都是一场噩梦。从珍贵的照片到重要的工作文件,这种损失可能会让人感到毁灭性。值得庆幸的是,几个 iPhone 数据恢复软件选项可以帮助您找回丢失的文件。这些工具提供不同的功能…

神经网络——非线性激活

1 非线性激活 1.1 几种常见的非线性激活: ReLU (Rectified Linear Unit)线性整流函数 Sigmoid 1.2代码实战: 1.2.1 ReLU import torch from torch import nn from torch.nn import ReLUinputtorch.tensor([[1,-0.5],[-1,3]])inputtorch.reshape(…

刷题DAY18

排序 题目:输入10个大小不同的整数,将它们从小到大排序后输出,并给出现每个元素在原来序列中的位置 输入:输入数据有一行,包含10个整数,用空格分开 输出:输出数据有两行,第一行为…

【计算机网络】名词解释--网络专有名词详解

在网络通信中,有许多专业术语和概念,它们共同构成了网络通信的基础。以下是一些常见的网络术语及其定义和相互之间的关系: 一、网络基础 1.1 电路交换:电路交换是一种在数据传输前建立专用通信路径的通信方式。在通信开始前&…

HTML+CSS+JavaScript制作动态七夕表白网页(含音乐+自定义文字)

源码介绍 这篇博客就享下前端代码如何实现HTMLCSSJavaScript制作七夕表白网页(含音乐自定义文字)。记事本打开源码文件可以进行内容文字之类的修改,双击html文件可以本地运行效果,也可以上传到服务器里面,重定向这个界面 源码效果 源码下载…

Django后端架构开发:缓存机制,接口缓存、文件缓存、数据库缓存与Memcached缓存

深入探讨Django后端架构中的缓存机制:接口缓存、文件缓存、数据库缓存与Memcached缓存 目录 🌟 缓存接口数据的实现✨ Django文件缓存的应用⚡ 关系型数据库缓存的策略💠 Memcached缓存的配置与优化 🌟 缓存接口数据的实现 在D…

前端开发工程师面试整理-ES6+的新特性

ES6(ECMAScript 2015)及后续版本引入了许多新特性,极大地增强了JavaScript的功能和开发体验。以下是一些主要的新特性: 变量声明 1. let 和 const: ● let 声明块作用域变量。 ● const 声明常量,值不能重新赋值。 ● 示例:

从行为面试问题(behavioral questions)看中美程序员差异。

中美程序员在职场中的工作状态和职能、福利等有很大区别,从面试中的BQ轮就可见一斑。 中美程序员的面试轮差异? 国内的面试轮在不同公司间差异很大,但总体的问题类型包含笔试面试(算法题、概念题、项目深挖、职业目标、职场文化…

混合现实UI优化:利用物理环境的直接交互

随着虚拟现实(VR)和混合现实(MR)技术的发展,用户界面(UI)的设计变得越来越重要,尤其是在需要适应多种物理环境的情况下。本文将介绍一种名为 InteractionAdapt 的用户界面优化方法,它专为VR环境中的工作空间适配而设计,能够有效利用物理环境,为用户提供更加灵活和个…

【仿真与实物设计】基于51单片机设计的打地鼠游戏机——程序源码原理图proteus仿真图PCB设计文档演示视频元件清单等(文末工程资料下载)

基于51单片机设计的打地鼠游戏机 演示视频: 基于51单片机设计的打地鼠游戏机 功能描述:使用 51单片机为核心制作一个打地鼠游戏机。按下启动开关,8盏LED流水点亮并闪烁2次,随即开始播放游戏音乐,直到开始选择模式。选择的模式在数码管上显示,该游戏机共有两个模式,分别…

layui table表单 checkbox选中一个其它也要选中

当我们选中其中一个商品的时候同类型的商品状态也要跟着改变 所以要在表单加载完成后去监听checkbox ,done:function (res) {console.log(详情表格数据,res)tableDetailList res.data;// 监听表格复选框选择table.on(checkbox( INST_SELECTORS.instLayFilters.unpaidTableDe…

TypeScript 面试题汇总

引言 TypeScript 是一种由微软开发的开源、跨平台的编程语言,它是 JavaScript 的超集,为 JavaScript 添加了静态类型系统和其他高级功能。随着 TypeScript 在前端开发领域的广泛应用,掌握 TypeScript 已经成为很多开发者必备的技能之一。本文…

Canvas 动画: atan2 三角函数与鼠标跟随效果

这个案例展示了如何使用HTML5的Canvas和JavaScript实现一个动态效果:在画布上绘制一个箭头,并让它实时跟随鼠标移动。这个小项目不仅有趣,还能帮助你理解编程和基本数学概念的实际应用。 项目需求 我们的目标是在一个画布上绘制一个箭头&…

HTML+CSS浮动和清除浮动的效果及其应用场景举例

一、清除浮动的效果 解释 .container:用于展示浮动和清除浮动效果的容器,具有边框和背景色以便于区分。 .float-box:浮动元素,用不同的背景色标识。 .clearfix:使用伪元素清除浮动的类,应用于第二个容器。 …

电脑浏览器打不开部分网页

电脑浏览器打不开部分网页 时间: 2024-08-25 问题描述: 电脑突然打不开部分网页 例如腾讯文档 夸克网盘 但其他网页能够正常打开 原因 可能为域名解析问题 更改DNS即可解决 解决办法 控制面板–> 网络和Internet—>网络连接—> WLAN----> 属性 —> Interne…

已解决:`javax.security.auth.RefreshFailedException` 刷新失败的正确解决方法,亲测有效!!!

在 Java 开发中,javax.security.auth.RefreshFailedException 异常通常涉及到安全认证和授权机制。这个异常通常在尝试刷新或更新认证凭证时出现问题,可能会影响到应用的安全性和稳定性。本文将详细分析该异常的原因,并提供有效的解决方法。 …

Kotlin 学习手册01 高阶函数

Kotlin 学习手册01 高阶函数 1 函数作为参数2 函数作为返回值3 测试代码 在Kotlin中,高阶函数(Higher-Order Functions)是指接受函数作为参数或返回一个函数的函数。这种特性允许你将函数像变量一样传递,从而使代码更加灵活和可重…

一元四次方程求解-【附MATLAB代码】

目录 前言 求解方法 MATLAB验证 附:一元四次方程的故事 前言 最近在研究机器人的干涉(碰撞)检测,遇到了一个问题,就是在求椭圆到原点的最短距离时,构建的方程是一个一元四次方程。无论是高中的初等数学…

文件包含漏洞案例

一、PHP://INPUT Example 1&#xff1a;造成任意代码执行 源代码&#xff1a; <meta charset"utf8"> <?php error_reporting(0); $file $_GET["file"]; if(stristr($file,"php://filter") || stristr($file,"zip://") |…