watch 和 watchEffect 的隐藏点 --- 非常细致

之前有一篇文章讲述了 watch 和 watchEffect 的使用,但在实际使用中,仍然存在一些“隐藏点”,可能会影响开发,在这补充一下。

1. watch 的隐藏点

1.1 性能陷阱:深度监听的影响

当在 watch 中使用 deep: true 来监听一个复杂的嵌套对象时,Vue 会递归遍历对象的所有属性,判断其是否发生变化。对于大规模或复杂的嵌套对象,这会产生显著的性能开销。尤其是在大型应用中,频繁使用深度监听可能导致性能下降。

1.2 初始值的处理

watch 默认是在监听的数据发生变化时执行,但可以通过设置 immediate: true 在初始化时立即执行一次回调,而不等待依赖项第一次变化后执行。这对于需要初始化时触发某些逻辑(如数据加载)的场景很有帮助。

然而,需要注意的是,immediate 执行时,旧值会是 undefined,这可能需要在回调中处理。

import { ref, watch } from 'vue'
const count = ref(0)
watch(() => count.value,(newValue, oldValue) => {if (oldValue === undefined) {console.log('oldValue is undefined')}console.log(`count changed from ${oldValue} to ${newValue}`)},{immediate: true}
)
count.value++

1.3 数组监听的行为

watch 默认对数组的变化进行“浅层”监听,即只有当数组的引用发生变化时才会触发回调。如果需要监听数组内部元素的变化,则需要使用 deep: true 或者单独监听数组元素。

import { ref, watch } from 'vue'
const numbers = ref([1, 2, 3])
watch(() => numbers.value,(newVal, oldVal) => {console.log('Array changed:', newVal)},{ deep: true }
)
numbers.value.push(4)

如果不使用 deep: true,watch 函数不会执行,但页面仍然改变,因为 numbers 是响应式数据。

1.4 延迟执行:flush

flush 选项:watch 默认在组件更新后执行回调(flush: 'post'),但可以通过设置 flush 选项来改变这一行为:

- pre:在 DOM 更新之前执行回调。

- post:在 DOM 更新之后执行回调(默认行为)。

- sync:在依赖项变化时立即同步执行回调。

这种设置可以帮助我们在特定的场景下更好地控制回调的执行时机,避免副作用与 DOM 更新过程的冲突。

举个 🌰

1)使用 post 

import { ref, watch } from 'vue'
const state = ref({ count: 1 })
watch(() => state.value.count,(newVal) => {console.log('依赖项更改后立即执行,newVal: ', newVal)},// 默认{ flush: 'post' }
)
console.log('start')
state.value.count++
console.log('end')

2)使用 sync

import { ref, watch } from 'vue'
const state = ref({ count: 1 })
watch(() => state.value.count,(newVal) => {console.log('依赖项更改后立即执行,newVal: ', newVal)},{ flush: 'sync' }
)
console.log('start')
state.value.count++
console.log('end')

3)使用 pre

import { ref, watch } from 'vue'
const state = ref({ count: 1 })
watch(() => state.value.count,(newVal) => {console.log('依赖项更改后立即执行,newVal: ', newVal)},{ flush: 'pre' }
)
console.log('start')
state.value.count++
console.log('end')

看到 👀 这里可能感觉很疑惑,为什么 flush : pre 和 post 的结果一样呢?

在这个代码中,所有的操作都是同步执行的。state.value.count++ 是同步的赋值操作,而 console.log 也是同步的。所以当 state.value.count++ 执行时,watch 已经在收集依赖并准备好在数据变化后执行回调了。

Vue 采用的是异步 DOM 更新机制,也就是说,DOM 的更新是批处理的,通常在事件循环的下一次 tick 中才会实际执行。因此,pre 和 post 的区别主要体现在视图更新的时机上,而不是数据更新的时机。由于视图更新还没有真正发生,所以从表面上看,二者的执行时机并没有明显的区别。

为了更好的看到变化,在控制台改变数据。

import { ref, watch } from 'vue'
const state = ref({ count: 1 })
watch(() => state.value.count,(newVal) => {console.log('依赖项更改后立即执行,newVal: ', newVal)console.log('DOM内容:', document.querySelector('div').textContent)},{ flush: 'pre' }
)
window.state = state

import { ref, watch } from 'vue'
const state = ref({ count: 1 })
watch(() => state.value.count,(newVal) => {console.log('依赖项更改后立即执行,newVal: ', newVal)console.log('DOM内容:', document.querySelector('div').textContent)},{ flush: 'post' }
)
window.state = state

1.5 多个依赖

当使用 watch 监听多个依赖时,必须注意依赖的声明顺序。如果将多个依赖组合在一起使用(如数组形式),回调函数只会在任一依赖变化时触发,而不会区分具体哪个依赖发生了变化。 

import { ref, watch } from 'vue'
const count = ref(0);
const message = ref('Hello World');watch([count, message], ([newCount, newMessage], [oldCount, oldMessage]) => {console.log('Either count or message changed');console.log('New count:', newCount, 'Old count:', oldCount);console.log('New message:', newMessage, 'Old message:', oldMessage);
});window.count = count
window.message = message

2. watchEffect 的隐藏点

2.1 未提供旧值

与 watch 不同,watchEffect 无法获取依赖项的旧值,因为它的设计是为了简化依赖追踪和副作用处理。如果逻辑依赖于新旧值的对比,改用 watch 解决。

2.2 停止侦听的管理

watchEffect 返回一个停止监听的函数,用于手动停止监听,但在复杂场景中,开发者容易忽略在组件销毁时手动调用停止函数,可能导致未预期的副作用持续存在。

const stop = watchEffect(() => {console.log('Running effect');// 假设这里创建了一些副作用(如定时器、网络请求)
});stop(); //  需要在适当时机调用 stop 停止侦听
2.3 执行顺序与依赖追踪 

watchEffect 会在组件渲染后立即执行,并自动追踪所有在函数中读取的响应式数据。当这些数据发生变化时,watchEffect 将重新执行整个函数。

注意⚠️,watchEffect 在执行时可能会比组件的生命周期钩子(如 onMounted)更早,这可能导致依赖项尚未完全初始化,某些预期的行为未发生。

举个 🌰

<template><div>{{ data.message }}</div>
</template><script setup>
import { ref, watchEffect, onMounted } from 'vue';
const data = ref({message: '',
});watchEffect(() => {console.log('watchEffect triggered, message:', data.value.message);
});onMounted(() => {console.log('Component mounted');data.value.message = 'Hello, world!';
});
</script>

因为 watchEffect  在 onMounted 之前执行,也就是组件初次渲染之后立即执行,此时 message 为空字符串。只有当 onMounted 钩子函数触发并更改了 message 值后,watchEffect 再次触发,此时 message 为 'Hello,world'。

如果想避免该情况,使用 watch 即可。

watch(() => data.value.message, (newValue) => {console.log('message changed:', newValue);
});

3. 共同问题

尽量避免出现下面情况:

3.1. 嵌套监听

如果在一个 watch 或 watchEffect 中嵌套使用另一个监听器,需要小心管理它们之间的依赖关系和停止条件,以避免复杂的嵌套监听逻辑造成难以调试的错误。

3.2 意外的副作用顺序

由于 Vue 3 响应式系统是异步批处理的,多个 watch 或 watchEffect 的回调函数可能会在同一帧中被调度和执行。这可能导致副作用之间的执行顺序不一致,尤其是在处理多个依赖数据的变化时。

4. 实际使用建议

1、优先使用 watchEffect:在逻辑简单、无条件依赖、无需旧值对比的场景中,watchEffect 更为简洁高效。

2、使用 watch 处理复杂依赖:如果场景中需要深度监听、处理复杂逻辑、对比新旧值,或者避免无限循环时,优先选择 watch。

3、管理依赖和副作用:尽量将复杂的逻辑抽离出来,避免在 watchEffect 或 watch 中直接处理过多的业务逻辑,避免引发意外的依赖问题。

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

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

相关文章

[MRCTF2020]套娃1

打开题目&#xff0c;查看源代码&#xff0c;有提示 有两层过滤 1.过滤"_"与"%5f" 。 这里要求的参数必须是"b_u_p_t"但是不能检测出"_"。这里看着很作弄人。其实这里要用到php里非法参数名的问题。可以参考一下博客 ?b.u.p.t2333…

Python爬虫技术与K-means算法的计算机类招聘信息获取与数据分析

有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主 目录 摘要.... 1 Abstract 2 1 引言.... 3 1.1 研究背景... 3 1.2 国内外研究现状... 4 1.3 研究目的... 5 1.4 研究意义... 7 2 关键技术理论介绍... 7 2.1 Python爬虫... 7 2.1 K-means…

微软开源库 Detours 详细介绍与使用实例分享

目录 1、Detours概述 2、Detours功能特性 3、Detours工作原理 4、Detours应用场景 5、Detours兼容性 6、Detours具体使用方法 7、Detours使用实例 - 使用Detours拦截系统库中的UnhandledExceptionFilter接口&#xff0c;实现对程序异常的拦截 C软件异常排查从入门到精通…

VMware虚拟机下安装Ubuntu22.04以及汉化配置保姆级教程

目录 一.VMware和Ubuntu下载 二.在VMware中创建Ubuntu 1.点击 创建新的虚拟机 2.选择典型 3.选择Ubuntu镜像包&#xff08;自定义存放的位置&#xff09; 4.创建个人信息&#xff08;密码一定要牢记&#xff09; 5.选择虚拟机的安装位置 6.其他配置项&#xff08;默认下…

Unity Obfuscator 使用说明

一、Assembly - Settings 1. 核心Unity程序集&#xff08;Assembly-CSharp&#xff09; Obfuscate Assembly-CSharp: 开启 这是Unity的核心程序集&#xff0c;所有没有存储在程序集定义文件&#xff08;assembly definition file&#xff09;中的代码都会被存储在这里。大多数…

C++多态详解

1. 多态的概念 多态就是函数调用的多种形态&#xff0c;使用多态能够使得不同的对象去完成同一件事时&#xff0c;产生不同的动作和结果。 举个栗子&#xff1a;比如买票这个行为&#xff0c;当普通人买票时&#xff0c;是全价买票&#xff1b;学生买票时&#xff0c;是半价买…

yolov5网络初始化问题

当你打印detect层的三个特征层时&#xff0c;发现有三种不同的长和宽&#xff0c;如下图所示&#xff1a; 我提出三个问题&#xff1a; 为什么不一样呢&#xff0c;输入有什么含义吗&#xff1f; 为什么网络初始化四次&#xff08;forward)&#xff1f; 下面来逐个击破 1. torc…

uniapp 微信小程序生成水印图片

效果 源码 <template><view style"overflow: hidden;"><camera device-position"back" flash"auto" class"camera"><cover-view class"text-white padding water-mark"><cover-view class"…

【笔记】泰山派环境配置遇到E: Unable to locate package repo

答案来自通义千问&#xff0c;解决了我的问题&#xff0c;做一些记录 你尝试在Ubuntu或Debian系统上使用apt命令安装repo工具&#xff0c;但是遇到了问题&#xff0c;因为repo不是直接在软件源中作为一个独立的包提供的。repo是Google的一个Git仓库管理工具&#xff0c;通常用…

EasyCVR视频汇聚平台:打造全栈视频监控系统的基石,解锁可视化管理与高效运维

随着科技的飞速发展&#xff0c;视频监控已成为现代社会不可或缺的一部分&#xff0c;广泛应用于社区、公共场所、工业领域等多个场景。EasyCVR视频汇聚平台&#xff0c;作为一款高性能的视频汇聚管理平台&#xff0c;凭借其强大的视频处理、汇聚与融合能力&#xff0c;在构建全…

数据结构——关于栈

1.栈 1.1栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作 进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出的原则 比如&#xff1a;羽毛球桶&#xff0c;弹夹等等 压…

苍穹外卖项目DAY05

苍穹外卖项目DAY05 1、店铺营业状态设置 1.1、Redis入门 Redis简介 Redis是一个基于内存的key-value结构数据库 基于内存存储&#xff0c;读写性能高适合存储热点数据&#xff08;热点商品、咨询、新闻&#xff09;企业应用广泛 中文网&#xff1a;https://www.redis.net…

【Java学习】Stream流详解

所属专栏&#xff1a;Java学习 Stream流是JDK 8引入的一个概念&#xff0c;它提供了一种高效且表达力强的方式来处理数据集合&#xff08;如List、Set等&#xff09;或数组。Stream API可以以声明性方式&#xff08;指定做什么&#xff09;来处理数据序列。流操作可以被分为两大…

[C++][opencv]基于opencv实现photoshop算法灰度化图像

测试环境】 vs2019 opencv4.8.0 【效果演示】 【核心实现代码】 BlackWhite.hpp #ifndef OPENCV2_PS_BLACKWHITE_HPP_ #define OPENCV2_PS_BLACKWHITE_HPP_#include "opencv2/core.hpp"namespace cv {class BlackWhite { public:float red; //红色的灰度系…

阿里云ubuntu系统安装mysql8.0

一、安装mysql8.0 1.已安装其他版本的mysql&#xff0c;需要删除 若没有不需要此操作 1 #卸载MySQL5.7版本 2 apt remove -y mysql-client5.7* mysql-community-server5.7* 4 # 卸载5.7的仓库信息 5 dpkg-l | grep mysql | awk iprint $2} | xargs dpkg -P2.更新仓库 apt u…

Python酷库之旅-第三方库Pandas(084)

目录 一、用法精讲 351、pandas.Series.str.isdigit方法 351-1、语法 351-2、参数 351-3、功能 351-4、返回值 351-5、说明 351-6、用法 351-6-1、数据准备 351-6-2、代码示例 351-6-3、结果输出 352、pandas.Series.str.isspace方法 352-1、语法 352-2、参数 3…

0815,析构函数,拷贝构造函数,赋值运算符函数

来自同济医院的问候 目录 01&#xff1a;对象创建 001.cc 003size.cc 02&#xff1a;对象销毁 004pointer.cc 005destroytime.cc 03&#xff1a;本类型对象的复制 3.1 拷贝构造函数 006cp.cc 007cptime.cc 008recursion.cc 009rightleft.cc 3.2 赋值运算符函数 …

平安城市/雪亮工程现状及需求分析:EasyCVR视频汇聚平台助力雪亮工程项目建设

一、背景现状 经过近几年的努力&#xff0c;平安城市雪亮工程建设取得了显著的成绩&#xff0c;完成了前端高清视频点位和高清卡口系统建设&#xff0c;建成了&#xff08;视频监控类&#xff09;、&#xff08;卡口类&#xff09;和&#xff08;应用类&#xff09;的平台。这…

BvSP_ Broad-view Soft Prompting for Few-Shot Aspect Sentiment Quad Prediction

中文题目: 英文题目: BvSP: Broad-view Soft Prompting for Few-Shot Aspect Sentiment Quad Prediction 论文地址: aclanthology.org/2024.acl-long.460.pdf 代码地址: https://github.com/byinhao/BvSP 论文级别&#xff0c; 单位: (2024 ACL long paper) 南开大学&#xff0…

Fortify三种扫描模式有什么区别?分别怎么用?

一、通过“Audit Workbench”进行测试 “Audit Workbench”支持Java语言源代码的测试。 二、通过“Scan Wizard”进行测试 “Scan Wizard”支持Java、Python、C/C、.Net、Go、PHP、Flex、Action Script、HTML、XML、JavaScript、TypeScript、Kotlin、SQL、ABAP、ColdFusion语言…