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

本文主要是介绍解读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类实现。

这篇关于解读vue3源码-响应式篇3 effect副作用函数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1109064

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

MCU7.keil中build产生的hex文件解读

1.hex文件大致解读 闲来无事,查看了MCU6.用keil新建项目的hex文件 用FlexHex打开 给我的第一印象是:经过软件的解释之后,发现这些数据排列地十分整齐 :02000F0080FE71:03000000020003F8:0C000300787FE4F6D8FD75810702000F3D:00000001FF 把解释后的数据当作十六进制来观察 1.每一行数据

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能