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

news2024/9/22 17:17:55

之前有一篇文章讲述了 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.coloradmin.cn/o/2047121.html

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

相关文章

多模态大模型中的幻觉问题及其解决方案

人工智能咨询培训老师叶梓 转载标明出处 多模态大模型在实际应用中面临着一个普遍的挑战——幻觉问题&#xff08;hallucination&#xff09;&#xff0c;主要表现为模型在接收到用户提供的图像和提示时&#xff0c;可能会产生与图像内容不符的描述&#xff0c;例如错误地识别颜…

Windows下pip install mysqlclient安装失败

有时候安装mysqlclient插件报如下错误 提示先安装mysqlclient的依赖wheel文件 下载链接(必须对应版本&#xff0c;python3.6版本对1.4.4版本) 如下选择历史版本 mysqlclient官网 https://pypi.org/project/mysqlclient/python3.6对应版本 https://pypi.org/project/mysqlcl…

网络安全实训第一天(dami靶场搭建,XSS、CSRF、模板、任意文件删除添加、框架、密码爆破漏洞)

1.环境准备&#xff1a;搭建漏洞测试的基础环境 安装完phpstudy之后&#xff0c;开启MySQL和Nginx&#xff0c;将dami文件夹复制到网站的根目录下&#xff0c;最后访问安装phptudy机器的IP地址 第一次登录删除dami根目录下install.lck文件 如果检测环境不正确可以下载php5.3.2…

ubuntu20 lightdm无法自动登录进入桌面

现象&#xff1a;在rk3568的板子上自己做了一个Ubuntu 20.04的桌面系统。配置lightdm自动登录桌面&#xff0c;配置方法如下&#xff1a; $ vim /etc/lightdm/lightdm.conf [Seat:*] user-sessionxubuntu autologin-userusername #修改成自动登录的用户名 greeter-show-m…

如何做萤石开放平台的物联网卡定向?

除了用萤石自带的4G卡外&#xff0c;我们也可以自己去电信、移动和联通办物联网卡连接萤石云平台。 1、说在前面 注意&#xff1a;以下流程必须全部走完&#xff0c;卡放在设备上才能连接到萤石云平台。 2、大致流程 登录官网→下载协议→盖章&#xff08;包括骑缝章&#…

Hyperf 安装,使用,

安装&#xff0c; 一般开发都是windows,所以用虚拟机或docker 使用 启动 php bin/hyperf.php start如果出现端口被占用&#xff0c;下面的处理方法 查看9501端口那个进程在占用 netstat -anp|grep 95012. kill掉 kill 18然后再启动即可 热更新 Watcher 组件除了解决上述…

【免费】最新区块链钱包和私钥的助记词碰撞器,bybit使用python开发

使用要求 1、用的是google里面的扩展打包成crx文件&#xff0c;所以在使用之前你需要确保自己电脑上有google浏览器&#xff0c;而且google浏览器版本需要在124之上。&#xff08;要注意一下&#xff0c;就是电脑只能有一个Chrome浏览器&#xff09; 2、在win10上用vscode开发…

网络编程:OSI协议,TCP/IP协议,IP地址,UDP编程

目录 国际网络通信协议标准&#xff1a; 1.OSI协议&#xff1a; 2.TCP/IP协议模型&#xff1a; 应用层 &#xff1a; 传输层&#xff1a; 网络层&#xff1a; IPV4协议 IP地址 IP地址的划分&#xff1a; 公有地址 私有地址 MA…

jmeter引入jar包的三种方式

示例 实现对登录密码进行MD5加密 pom文件依赖 <!-- https://mvnrepository.com/artifact/commons-codec/commons-codec --><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.12&l…

安全密码算法:SM3哈希算法介绍

最靠谱的是看标准文档&#xff01; 1. 简介 国密算法之一&#xff0c;哈希算法的一种&#xff0c;也是密码杂凑算法。可以将不定长的输入消息message&#xff0c;经过SM3算法计算后输出为32B固定长度的哈希值&#xff08;hash value&#xff09;。哈希算法的实质是单向散列函…

Java | Leetcode Java题解之第343题整数拆分

题目&#xff1a; 题解&#xff1a; class Solution {public int integerBreak(int n) {if (n < 3) {return n - 1;}int quotient n / 3;int remainder n % 3;if (remainder 0) {return (int) Math.pow(3, quotient);} else if (remainder 1) {return (int) Math.pow(3…

使用 Python 进行 PDF 文件加密

使用 Python 解密加密的 PDF 文件-CSDN博客定义一个名为的函数&#xff0c;该函数接受三个参数&#xff1a;输入的加密 PDF 文件路径input_pdf、输出的解密 PDF 文件路径output_pdf和密码password。https://blog.csdn.net/qq_45519030/article/details/141256661 在数字化时代…

[Linux][OS][详解信号的产生]

目录 1.信号概念 硬件层面 2. 产生! 1. 键盘组合键 2. kill 命令 kill -signo pid 3. 系统调用 4. 硬件异常--会自动退出 软件条件--闹钟 发送 信号和信号量没有任何的关系&#xff0c;就像老婆和老婆饼&#xff0c;上一篇文章我们讲到了信号量&#xff0c;这篇文章我…

探索未来教育新形态:基于AI大模型的在线辅导平台LlamaTutor

在数字化时代,教育的边界正在被重新定义。今天,我们将深入探索一款创新的教育工具——LlamaTutor,一个基于AI大模型的在线个人辅导平台,它利用前沿技术为学习者带来前所未有的个性化学习体验。 引言 随着人工智能技术的飞速发展,AI在教育领域的应用日益广泛。LlamaTutor…

冰岛数据中心技术三巨头推出由可再生能源驱动的一体化云计算解决方案

冰岛通过国内生产的各种形式的可再生能源来满足其大部分能源需求。据三家开发新数据中心服务的公司称&#xff0c;这个北欧岛国也是关键任务云应用的理想环境。 Vespertec 公司、Sardina Systems 公司和 Borealis 公司共同开发了一种创新的 IT 解决方案&#xff0c;名为冰云综合…

MATLAB算法实战应用案例精讲-【人工智能】差分隐私(概念篇)

目录 前言 知识储备 算法原理 发展历程 差分隐私的引入 GIC 事件 ε(epsilon)-差分隐私​编辑 实现方式 什么是差分隐私 差分隐私的工作原理 数学模型 差分隐私计算公式 拉普拉斯机制 高斯机制 高斯机制满足 (ε,δ)-差分隐私的数学证明 可组合性 怎样在机…

Python版《超级玛丽+源码》-Python制作超级玛丽游戏

小时候最喜欢玩的小游戏就是超级玛丽了&#xff0c;有刺激有又技巧&#xff0c;通关真的很难&#xff0c;救下小公主还被抓走了&#xff0c;唉&#xff0c;心累&#xff0c;最后还是硬着头皮继续闯&#xff0c;终于要通关了&#xff0c;之后再玩还是没有那么容易&#xff0c;哈…

思科OSPF动态路由配置8

#路由协议实现# #任务八OSPF动态路由配置8# 开放式最短路径优先&#xff08;Open Shortest Path First,OSPF&#xff09;协议是目前网络中应用最广泛的动态路由协议之一。它也属于内部网关路由协议&#xff0c;能够适应各种规模的网络环境&#xff0c;是典型的链路状态路由协…

JavaScript初级——简介

一、什么是语言 1、计算机就是一个由人来控制的机器。 2、我们要学习的语言就是人和计算机交流的工具&#xff0c;人类通过语言来控制、操作计算机。 3、编程语言和我们说的中文、英文本质上没有区别&#xff0c;只是语法比较特殊。 4、语言的发展&#xff1a; —纸带机&#x…

“低代码” 风暴:重塑软件开发新未来

目录 引言&#xff1a; 正文&#xff1a; 方向一&#xff1a;技术概览 方向二&#xff1a;效率与质量的权衡 方向三&#xff1a;挑战与机遇 结束语&#xff1a; 引言&#xff1a; 在当今数字化高速发展的时代&#xff0c;技术的创新如同璀璨星辰不断照亮我们前行的道路。“…