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.hqwc.cn/news/589743.html

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

相关文章

【Linux】详解动静态库的制作和使用动静态库在系统中的配置步骤

一、库的作用 1、提高开发效率,让开发者所有的函数实现不用从零开始。 2、隐藏源代码。 库其实就是所有的.o文件用特定的方式进行打包形成一个文件,各个.o文件包含了源代码中的机器语言指令。 二、动态库和静态库的制作和使用 2.1、静态库的制作和使用…

美创科技获浙江省网络空间安全协会多项荣誉认可

4月2日,浙江省网络空间安全协会第二届会员大会第一次会议在杭州隆重召开,近180家会员单位代表、数十位特邀专家、嘉宾莅临现场。浙江省委网信办副主任马晓军出席会议并致辞,本次大会由协会秘书长吴铤主持。 凝心聚力,继往开来&…

ViveNAS性能调试笔记(一)

ViveNAS是一个开源的NAS文件服务软件,有一套独立自创的架构,ViveNAS希望能做到下面的目标: - 能支持混合使用高性能的介质(NVMe SSD)和低性能介质(HDD,甚至磁带)。做到性能、成本动态均衡。因此ViveNAS使用…

C++--类的定义

一.类的定义 class 类名 { private:成员属性或成员函数 protected:成员属性或成员函数 public:成员属性或成员函数 };补充: (1)class是声明类的关键字,class后跟类名。类名一般首字母大写。 (2)类包括成员…

怎样在Linux搭建NTP服务器

搭建 NTP(Network Time Protocol)服务器可以帮助你在局域网内提供时间同步服务,让网络中的设备都使用统一的时间。以下是在 Linux 系统上搭建 NTP 服务器的基本步骤: 安装 NTP 服务器软件: 在终端中执行以下命令安装 N…

【爬虫框架Scrapy】02 Scrapy入门案例

接下来介绍一个简单的项目,完成一遍 Scrapy 抓取流程。通过这个过程,我们可以对 Scrapy 的基本用法和原理有大体了解。 1. 本节目标 本节要完成的任务如下。 创建一个 Scrapy 项目。 创建一个 Spider 来抓取站点和处理数据。 通过命令行将抓取的内容…

Gemini即将收费,GPT无需注册?GPT3.5白嫖和升级教程

🌐Gemini 即将开始收费 开发者“白嫖”的好日子到头了 - Gemini将开始收费,影响使用Google AI for Developers提供的Gemini API的用户。 - Gemini API将引入按量付费定价,需要注意新的服务条款。 - 用户需在5月2日之前停止使用Gemini API和Go…

redis事务(redis features)

redis支持事务,也就是可以在一次请求中执行多个命令。redis中的事务主要是通过MULTI和EXEC这两个命令来实现的。 MULTI命令用来开启一个事务,事务开启之后,所有的命令就都会被放入到一个队列中,最后通过一个EXEC命令来执行事务中…

【现代C++】委托构造函数

现代C中的委托构造函数(Delegating Constructors)是C11引入的特性,它允许一个构造函数调用同一个类中的另一个构造函数,以避免代码重复。这种特性在初始化对象时提高了代码的复用性和清晰性。 1. 基本用法 在同一个类中&#xf…

dhcp中继代理

不同过路由器分配ip了,通过一台服务器来代替,路由器充当中继代理功能,如下图 服务器地址:172.10.1.1/24 配置流程: 1.使能dhcp功能 2.各个接口网关地址,配置dhcp中继功能 dhcp select relay &#xff0…

Markdown介绍

一.Markdown基本介绍🍗 Markdown 是一种轻量级标记语言,用于简单、易读易写的文本格式编写。它设计初衷是让人们能够使用普通文本编辑器编写格式简单的文档,并且可以转换成有效的HTML。Markdown 的语法非常简洁直观,通过使用特定…

VS2022配置boost库-Windows为例

1. boost库下载 1)下载boost库源码:https://www.boost.org/ 2)以1.81版本为例,安装包如下 3)下载后解压 比如我是放在E盘下面的boost文件夹 2. 安装配置 1)打开VS2022命令行 2)切换安装…