vue3源码解析——watch和watchEffect区别

watchwatchEffect是Vue 3.0中新增的两个响应式API,用于监听数据的变化。watch适用于需要获取新值和旧值,或者需要懒执行的场景,而watchEffect适用于需要监听多个数据源,并且需要立即执行的场景。它们之间的区别如下:

  1. watch是监听单个数据源,可以设置immediatedeep选项,可以获取新值和旧值;watchEffect则是监听一组数据源,不能设置immediatedeep选项,不能获取新值和旧值。
  2. watch是懒执行的,只有在数据变化时才会执行回调函数,而watchEffect则是立即执行的,不管数据是否变化。
  3. watch可以监听计算属性,而watchEffect不能监听计算属性。

watch和watchEffect用法区别

watchwatchEffect在使用上也有一些区别,具体如下:

watch的使用方法:

watch(source, callback, options)

其中,source可以是一个响应式数据源,也可以是一个函数,返回一个响应式数据源。callback是回调函数,会在数据变化时执行,可以获取新值和旧值。options是一个对象,可以设置immediatedeep等选项。

 watch使用示例:

import { ref, watch} from 'vue'const count = ref(0)// 使用watch监听数据变化,并在下一次DOM更新前执行回调函数
watch(count, (newVal, oldVal) => {console.log('count变化了', newVal, oldVal)
}, {flush: 'pre'
})// 修改数据
count.value++

watchEffect的使用方法:

watchEffect(callback, options)

其中,callback是一个函数,会在组件渲染时执行,并且会在数据变化时重新执行。options是一个对象,可以设置flush选项。

需要注意的是,watchEffect的回调函数中可以直接使用响应式数据源,不需要显式获取新值和旧值,因为watchEffect会自动收集依赖,在数据变化时重新执行回调函数。

watchEffect示例

import { ref, watchEffect } from 'vue'const count = ref(0)// 使用watchEffect监听数据变化,默认在下一次DOM更新后执行回调函数
watchEffect(() => {console.log('count2变化了', count.value)
})// 修改数据
count.value++

源码分析

watch和watchEffect相关源码都在core-main\packages\runtime-core\src\apiWatch.ts文件中

watchEffect方法

在源码里,watchEffect是一个函数,接收两个参数,第一个是effect,也就是用户传入的执行函数。第二个是options,设置flush属性,flush属性表示回调函数的执行时机。默认 flush 选项是 pre,即在依赖变化之后立即执行副作用函数。

watchEffect内部调用了doWatch方法,doWatch方法接收三个参数,watchEffect在调用的doWatch时候将第二个参数设为null 。这里是重点,为什么,因为等下看watch实现的时候就知道了,watch也调用了doWatch方法。只不过第二个参数不为null了。watchEffect先看到这,你就记住要去调doWatch方法。接下来去看doWatch怎么实现的。

flush具体有以下几个选项:

  • 'pre':在下一次 DOM 更新前执行回调函数(默认选项)。
  • 'post':在下一次 DOM 更新后执行回调函数。
  • 'sync':立即执行回调函数。

watchPostEffect和watchSyncEffect 方法

 值得注意的是,这个watchEffect方法下面,还对外抛出了两个方法,watchPostEffect和watchSyncEffect。前面说了通过flush属性控制effect执行的时机,watchEffect默认是flush:pre,DOM 更新前执行。如果你想改变flush,除了在watchEffect改变flush的值,还可以直接调watchPostEffect和watchSyncEffect方法。

watch方法

在源码里,watch是一个函数,接收三个参数,第一个是source,也就是用户的要监听的属性。第二个是回调函数,通常使用箭头函数,监听新旧值的变化并执行内部的方法。第三个是options,可以设置flush\immediate\deep\once属性。默认 flush 选项也是 pre,即在依赖变化之后立即执行副作用函数。

watch内部也调用了doWatch方法,doWatch方法接收watch传入的三个参数。

 

核心doWatch方法

从前面的分析可以看到,watchEffect和watch都执行了doWatch方法。唯一不同的是watchEffect使用doWatch时第二个参数使用的是null。那么看下doWatch的执行逻辑吧

doWatch接收的参数

doWatch方法接收三个参数:source、cb、options。具体解释如下:

souce:要监听的数据源,可以是一个 ref 对象、一个 reactive 对象、一个数组或一个函数。

cb:是回调函数,在watch中使用。(newValue,oldValue)=>{}。

options:是一个配置对象,接收以下属性

  • deep:是否深度监听,默认值为 false。如果设置为 true,则会递归地监听 source 对象的所有属性和子属性。
  • immediate:是否立即执行回调函数,默认值为 false。如果设置为 true,则会在监听器被创建时立即执行一次回调函ctions。
  • flush:指定回调函数的刷新时机,可以是 'pre''post''sync',默认值为 'pre'
  • onTrack:一个函数,用于在追踪依赖时调用。
  • onTrigger:一个函数,用于在触发依赖时调用。

 

getter 函数——获取监听数据执行结果

doWatch方法第一个核心是定义一个getter函数,用于获取需要监听的数据。getter 的值是根据 source 的不同类型而得到。

  1. 如果 source 是一个 ref 对象,那么 getter 返回 source.value
  2. 如果 source 是一个 reactive 对象,那么 getter 返回 reactiveGetter(source)
  3. 如果 source 是一个数组,那么 getter返回一个数组,其中每个元素是一个值或一个函数的执行结果。具体来说,如果数组中的某个元素是一个 ref 对象,那么返回 s.value,如果是一个 reactive 对象,那么返回 reactiveGetter(s),如果是一个函数,那么返回 callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
  4. 如果 source 是一个函数,那么 getter 的值取决于 cb 的值:
    • 如果 cb 存在,那么 getter 的值是一个函数,返回 callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
    • 否则没有cbgetter 的值是一个函数,返回一个值或一个函数的执行结果。(也就是watchEffect)具体来说,如果 cleanup 存在,那么首先执行 cleanup(),然后返回 callWithAsyncErrorHandling(source, instance, ErrorCodes.WATCH_CALLBACK, [onCleanup])

 effect——响应式数据依赖管理

doWatch 的第二个关键是通过new Reactive()创建一个effect实例。接收getter、NOOP、scheduler。getter是一个函数,根据source不同返回一个值或一个函数的执行结果。NOOP是一个空的箭头函数。scheduler是任务job的调度器,默认flush:pre走的是queueJob任务队列调度器,通过队列管理任务,先进先出。

 这个effect是什么?scheduler是什么?queueJob(job)又是什么呢?接着看源码

ReactiveEffect作用——依赖收集

这个ReactiveEffect是实现vue3响应式数据更新的核心类。当数据变化时,可以通过 trigger 函数触发所有依赖该数据的效果,从而更新相关的视图。reactiveEffect 类的作用包括:

  1. 构造函数:接受四个参数,分别是用于执行的函数 fn、触发更新的函数 trigger可选的调度器函数 scheduler 和可选的效果作用域 scope
  2. dirty 属性:一个只读属性,表示当前的脏标记,可以是 DirtyLevels.NotDirtyDirtyLevels.DirtyDirtyLevels.MaybeDirtyDirtyLevels.QueryingDirty 之一。
  3. run 方法:执行当前效果,即调用 fn 函数,并在执行前后进行一些必要的操作,如更新脏标记、记录活跃的效果等。
  4. stop 方法:停止当前效果,即取消对 fn 函数的依赖,并进行一些必要的清理操作。

调度器scheduler——管理job的工具

Vue 3 中的 scheduler.ts 文件是用来实现调度器(scheduler)功能的。调度器在 Vue 中负责管理异步更新的调度和执行,确保在适当的时机更新组件以及处理副作用函数。具体来说,scheduler.ts 文件包含了以下主要功能:

  1. 调度更新:调度器负责管理组件的更新调度,根据更新的优先级和策略来决定何时执行更新操作,以提高性能和效率。

  2. 异步更新:调度器可以将更新操作异步执行,通过微任务或宏任务来延迟更新操作,以避免阻塞主线程,提高页面的响应性。

  3. 副作用函数的调度:调度器还负责管理副作用函数的执行时机,根据副作用函数的依赖关系和执行策略来调度副作用函数的执行。

  4. 性能优化:调度器可以根据具体情况对更新操作进行优化,比如批量更新、合并更新等操作,以减少不必要的更新和提高性能。

queueJob 函数来判断当前任务是否已存在于任务队列中,如果不存在,则将当前任务插入到任务队列中。当任务队列中的任务数量达到一定的阈值时,scheduler 函数会通过 queueFlush 函数来执行任务队列中的任务。 

watch和watchEffect的核心区别——job函数做了什么

前面经过分析,doWatch拿到了三个参数(source,cb,options);然后根据source的不同,定义了一个getter函数,得到了监听数据的执行结果。然后又定义了一个effect管理依赖收集,通过effect的dirty判断依赖的属性有没有变化,effect的run方法可以运行接收的第一个fn函数,在这里fn是getter函数。effect还有一个调度器函数,默认是queueJob(job)。queueJob用来管理一个任务队列。核心方法是看单个job是怎么实现的。

job的定义还是在doWatch方法内,可以看到,job先是对effect的active和dirty进行判断,如果都不满足,不需要重新执行方法。

接着判断cb,也就是doWatch接收的第二个参数。还记得吗,watch调用doWatch的时候第二个参数是回调函数(newValue,oldValue)=>{},而watchEffect调用的时候第二个参数传的是null。在这里可以看到watch和watchEffect的区别了!

如果cb存在,先去执行effect.run()方法,得到返回值给newValue,然后将newValue和oldValue拿到,执行回调方法cb

如果cb不存在,直接执行effect的run方法

 可以看到,watchEffect就少了一步,就是它不需要去处理新值和旧值的逻辑;如果在依赖的数据发生变化的时候,你必须想知道是哪个数据变化,并且旧值和新值的比较关系,那么你就用watch,如果你不关心旧值,也不关心是哪个数据项变化,你就用watchEffect。在doWatch里会将watchEffect的第一个参数当成getter,在effect执行run的时候去执行getter,也就是你传入watchEffect的函数。

总结

在这部分源码分析中,用了很长的篇幅取介绍doWatch的实现,因为watch和watchEffect都调用了doWatch,只是第二个参数不同,watchEffect传入的是null,watch传入的是cb。

doWatch干了什么事情呢?

首先由要收集数据依赖,收集的是哪写属性呢?

source传入的可能是ref\reactive属性、数组或者函数,这个时候需要对source进行计算,看最终依赖的是什么东西?通过定义一个getter函数,根据source类型不同,得到监听数据最后的执行结果。

怎么收集数据依赖呢?

使用ReactiveEffect类,定义一个effect实例,将前面定义的getter传进去,通过操作effect的run方法执行getter,通过effect的dirty属性判断getter是否改变。

数据改变了,doWatch怎么做呢?

数据发生变化了,也就是effect的dirty属性或者active属性有一个为true了。这时doWatch根据传入的第二个参数cb是否有值进行判断。

如果cb有值,那就是watch方法,watch是不是想知道依赖的getter发生变化后得到的新值和之前的旧值啊,所以cb有值的时候先去执行effect的run方法得到依赖属性变化后的新值newValue,将newValue和oldValue传入cb执行cb回调方法。

如果cb没值,那就是watchEffect方法。直接去执行effect的run方法,将传入的source转换为getter,执行就好了。

总的来说 watch vs watchEffect

watch 用于需要知道是哪个数据项发生变化以及需要比较旧值和新值的情况,因为它提供了旧值和新值的参数。而 watchEffect 则更适合在不关心旧值和具体数据项变化的情况下使用,因为它只关注副作用函数的执行,不提供旧值和新值的参数。

 看到这里,如果你已经稍微理解watch和watchEffect的区别了,请一键三连哦~

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

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

相关文章

CSS层叠样式表学习(文本属性)

(大家好,今天我们将继续来学习CSS文本属性的相关知识,大家可以在评论区进行互动答疑哦~加油!💕) 目录 四、CSS文本属性 4.1 文本颜色 4.2 对齐文本 4.3 装饰文本 4.4 文本缩进 4.5 行间距 4.6 文本…

苍穹外卖学习笔记(一)开发环境搭建

苍穹外卖技术框架 网关层: Nginx:反向代理负载均衡 nginx学习,看这一篇就够了:下载、安装。使用:正向代理、反向代理、负载均衡。常用命令和配置文件,很全-CSDN博客应用层: Spring Boot:简化spring程序的创…

计算机内存是如何管理的

计算内存的那些事儿——内存管理 大家回忆一下,计算机结构,或者说一个SoC(system-on-chip)芯片的结构。 cpu、memory、peripherals,这是计算机的主要部件,三者之间通过system bus勾搭在一起。 The main co…

第五篇:3.4 用户归因和受众(User attribution and audience) - IAB/MRC及《增强现实广告效果测量指南1.0》

翻译计划 第一篇概述—IAB与MRC及《增强现实广告效果测量指南》之目录、适用范围及术语第二篇广告效果测量定义和其他矩阵之- 3.1 广告印象(AD Impression)第三篇广告效果测量定义和其他矩阵之- 3.2 可见性 (Viewability)第四篇广…

Vulnhub:BOSSPLAYERSCTF: 1

目录 信息收集 arp nmap nikto whatweb WEB web信息收集 dirmap 命令执行漏洞 反弹shell 提权 系统信息收集 get root 信息收集 arp ┌──(root㉿ru)-[~/kali/vulnhub] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:50:56:2f:dd:9…

找不到api-ms-win-crt-runtime-l1-1-0.dll,无法继续执行代码的解决方法

当电脑系统中关键文件“api-ms-win-crt-runtime-l1-1-0.dll”发生丢失情况时,会导致某些应用程序无法正常运行。api-ms-win-crt-runtime-l1-1-0.dll是Windows操作系统中至关重要的动态链接库(DLL)文件,它包含了应用程序在运行过程…

【java探索之旅】逻辑控制掌握 顺序结构 分支语句

🎥 屿小夏 : 个人主页 🔥个人专栏 : Java编程秘籍 🌄 莫道桑榆晚,为霞尚满天! 文章目录 📑前言一、逻辑控制的概念二、顺序结构三、分支结构3.1 if语句3.2 if习题巩固3.3 细节注意项…

【个人笔记】如何用 Python 编写激活码解锁程序,方法二

目录 前言 第一步:编写激活码解锁程序(激活码.py) 第二步:修改需要解锁的程序(1.py) 总结 前言 在软件开发中,有时我们需要设计一种机制来保护程序,例如通过激活码来控制程序的…

Cortex-M7 异常处理与返回

1 前言 当CM3开始响应一个中断时,会在它小小的体内奔涌起三股暗流:  入栈: 把8个寄存器的值压入栈;  取向量:从向量表中找出对应的服务程序入口地址;  选择堆栈指针MSP/PSP,更新堆栈指针…

C语言 | Leetcode C语言题解之第2题两数相加

题目: 题解: struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2) {struct ListNode *head NULL, *tail NULL;int carry 0;while (l1 || l2) {int n1 l1 ? l1->val : 0;int n2 l2 ? l2->val : 0;int sum n1 n2 …

「每日跟读」英语常用句型公式 第3篇

「每日跟读」英语常用句型公式 第3篇 1. I don’t know how to ____ 我不知道如何_____ I don’t know how to play soccer (我不知道怎么踢足球) I don’t know how to study(我不知道如何学习) I don’t know how to play chess (我不知道如何下国…

使用 Kafka 保证消息不丢失的策略及原理解析

✨✨祝屏幕前的小伙伴们每天都有好运相伴左右,一定要天天开心!✨✨ 🎈🎈作者主页: 喔的嘛呀🎈🎈 目录 一、引言 二. 持久化存储 2.1持久化存储原理: 2.2使用示例: …

数据结构:详解【树和二叉树】

1. 树的概念及结构(了解) 1.1 树的概念 树是一种非线性的数据结构,它是由n(n>0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝…

Qt --- 常用控件

目录 1. 前言 2. QWidget核心属性 2.1. enabled(控件是否可用) 2.2. geometry(尺寸) 2.2.1. windowframe的影响 2.3. windowTitle(窗口标题) 2.4. windowIcon(窗口图标) 2.5. 使用qrc文件资源管理 2.6. windowOpacity(半透明效果) 2.7. cursor(设置鼠标光标) 2.8. fo…

DAY15|二叉树的层序遍历,226.翻转二叉树,101.对称二叉树

文章目录 二叉树的层序遍历226.翻转二叉树(层序遍历的衍生题)101.对称二叉树 二叉树的层序遍历 文字讲解:二叉树的层序遍历 视频讲解:二叉树的层序遍历 状态:看了文字讲解后理解了,熟悉队列来遍历每层子节点…

哈佛大学商业评论 --- 第四篇:一家公司的AR经验

AR将全面融入公司发展战略! AR将成为人类和机器之间的新接口! AR将成为人类的关键技术之一! 请将此文转发给您的老板! --- 专题作者:Michael E.Porter和James E.Heppelmann 虽然物理世界是三维的,但大多…

LIN总线基础

文章目录 1 什么是LIN 总线?1.1 LIN总线的历史 2.LIN总线的特点2.1 LIN总线的电气特性 3. 应用4 LIN总线基本概念4.1 LIN报文帧结构4.1.1 主节点与从节点4.1.2 调度表4.1.3网络管理4.1.4 帧头结构4.1.4.1 电平4.1.4.2 同步间隔段(间隔场)4.1.…

算法学习18:动态规划

算法学习18:动态规划 文章目录 算法学习18:动态规划前言一、线性DP1.数字三角形:f[i][j] max(f[i - 1][j - 1] a[i][j], f[i - 1][j] a[i][j]);2.1最长上升子序列:f[i] max(f[i], f[j] 1);2.2 打印出最长子序列3.最长公共子序…

免版权素材库:在营销和宣传中的重要性与应用

title: 免版权素材库:在营销和宣传中的重要性与应用 date: 2024/4/5 18:21:43 updated: 2024/4/5 18:21:43 tags: 免版权素材库营销宣传高质量素材节省成本避免侵权创意启发数字营销 免版权素材库在宣传和营销中的重要性不言而喻。在当今数字化时代,图片…

基于Python的微博舆论分析,微博评论情感分析可视化系统,附源码

博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇…