vue 3:监听器

目录

1. 基本概念

2. 侦听数据源类型

1. 监听getter函数

2. 监听 ref 或 reactive 的引用

3. 多个来源组成的数组

4. 避免直接传递值!!!

3. 深层侦听器

4. 立即回调的侦听器

5. 一次性侦听器

6. watchEffect()

7. 暂停、恢复和停止侦听器

8. 清理副作用

1. watch

2. watchEffect


1. 基本概念

在 Vue.js 中,**监听器(watchers)**是一种用于监听数据变化并在变化发生时执行相应操作的功能。监听器通常用于执行异步操作或复杂的逻辑计算,例如:监控数据变化,发起 API 请求,执行深层次的对象或数组变化监听等。

<template>
  <el-card>
    <p>{{ num }}</p>
    <input placeholder="Enter" type="text" v-model.lazy="num" />
  </el-card>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'

const num = ref(1)
// watch接收两个参数(数据源,回调函数)
watch(num, (newVal, oldVal) => {
  console.log(newVal, oldVal)
})
// 回调函数接收两个参数(新值,旧值)
</script>

上面的例子中,当input发生change事件时,num的值value会改变,同时监听器会监听到num的变化,并触发回调函数


2. 侦听数据源类型

watch 的第一个参数( "数据源"),形式上可以是ref,computed,reactive,getter,props或响应式对象。但本质上 必须是一个 getter 函数。这个限制是为了确保 watch 能够正确地监听数据的变化,同时提供更精确和高效的响应式更新。

1. 监听getter函数

const num1 = ref(1)
const num2 = ref(1)
watch(
  () => num1.value + num2.value,
  (a, b) => {
    console.log(a, b)
  },
)

2. 监听 ref 或 reactive 的引用

如果监听的对象是 ref 或 reactive 创建的引用,可以直接传入。

const count = ref(0);
watch(
  count,  // 直接传递 ref 引用
  (newCount, oldCount) => {
    console.log(`Count changed from ${oldCount} to ${newCount}`);
  }
)

3. 多个来源组成的数组

watch([num1, () => num2.value], ([new1, new2], [old1, old2]) => {
  console.log(new1, new2, old1, old2)
})

4. 避免直接传递值!!!

如果传递的是非响应式的普通值,Vue 无法监听它的变化。例如,

const state = reactive({
  user: { name: 'Alice', age: 25 },
})

watch(state.user.name, (newName, oldName) => {
  console.log(`User name changed from ${oldName} to ${newName}`)
})

控制台警告,并且并不能监听到 state.user.name的变化

传递 state.user.name 而不是 () => state.user.name ,因为此时 watch 直接接收了一个静态值,而不是可变的引用或 getter。

查了一下大概的原因:

因为watch底层逻辑依赖了proxy代理,而静态值不能被proxy代理的。

而之所以不用普通对象,而是用函数对象,则是和vue的响应式系统的依赖跟踪与收集有关。

 PS:这种坑很容易在props传值的时候踩

const props = defineProps({
  msg: {
    type: String,
    default: 'hello world',
  },
})
watch(
  () => props.msg,
  (newVal, oldVal) => {
    console.log(newVal, oldVal)
  },
)

3. 深层侦听器

官方:直接给 watch() 传入一个响应式对象,会隐式地创建一个深层侦听器——该回调函数在所有嵌套的变更时都会被触发

const count = reactive({
  num1: 0,
  num2: {
    a: 1,
    b: 2,
  },
})
watch(count, (newNum, oldNum) => {
  console.log(`Count changed from ${oldNum} to ${newNum}`)
  console.log(oldNum, newNum)
})

上面代码中,当num1或num2中的a、b发生改变时,打印的结果是

因为监听的数据源是一个对象,那么即使监听到对象持有值变了,新旧值也都是数据源对象的地址。与之相对,无论是对象的哪一个内容变化了,都相当于该对象变化了。

那如果我们监听num2呢

watch(count.num2, (newNum, oldNum) => {
  console.log(`Count changed from ${oldNum} to ${newNum}`)
  console.log(oldNum, newNum)
})

当然,同理,无论a还是b改变,都会被监听到。因为reactive也让num2具有了响应性

那如果我们使用getter来监听num2呢?

watch(
  () => count.num2,
  (newNum, oldNum) => {
    console.log(`Count changed from ${oldNum} to ${newNum}`)
    console.log(oldNum, newNum)
  },
)

那么无论改变a还是b,回调函数的都不会执行,因为getter返回的是num2对象的地址啊😂,地址不变,当然监听不到变化

当然,我们也可以这样,显式地加上 deep 选项,强制转成深层侦听器:

watch(
  () => count.num2,
  (newNum, oldNum) => {
    console.log(`Count changed from ${oldNum} to ${newNum}`)
    console.log(oldNum, newNum)
  },
  { deep: true },
)

vue 3.5+中,我们还可以设置深度监听的层数,如{ deep: 1},来限制最大遍历深度——即 Vue 应该遍历对象嵌套属性的级数

const count = reactive({
  num1: 0,
  num2: {
    a: 1,
    b: 2,
  },
})
watch(count, (newNum, oldNum) => {
  console.log(oldNum, newNum)
}, {
  deep: 1
})
count.num2.a++

比如上面的例子中,当我们改变num2.a的值时,侦听回调函数就不会执行


4. 立即回调的侦听器

watch 默认是懒执行的:仅当数据源变化时,才会执行回调。也就是说在创建创建侦听器时,并不会执行。但但在某些场景中,我们又会有这种需求。

比如:我们想请求一些初始数据,然后在相关状态更改时重新请求数据。

watch(
  source,
  (newValue, oldValue) => {
    // 立即执行,且当 `source` 改变时再次执行
  },
  { immediate: true }
)

5. 一次性侦听器

每当被侦听源发生变化时,侦听器的回调就会执行。但有时候会希望回调函数只在源变化时触发一次,那么就可以使用 once: true 选项。  注意:仅支持 vue 3.4 及以上版本 

watch(
  source,
  (newValue, oldValue) => {
    // 当 `source` 变化时,仅触发一次
  },
  { once: true }
)

6. watchEffect()

watchEffect 会自动收集所有在回调函数内使用的响应式数据,并在其中任何一个数据发生变化时重新运行回调函数。适合在不需要明确指定数据源的情况下自动收集依赖的数据。比如:

下面的代码,在每当 todoId 的引用发生变化时使用侦听器来加载一个远程资源:

const todoId = ref(1)
const data = ref(null)

watch(
  todoId,
  async () => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
    )
    data.value = await response.json()
  },
  { immediate: true }
)

注意:侦听器在这里两次使用了 todoId 的,一次是作为源,另一次是在回调中。

像这种情况,我们可以用 watchEffect 函数 来自动跟踪回调的响应式依赖。

watchEffect(async () => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
  )
  data.value = await response.json()
})

回调会立即执行,且不需要指定 immediate: true。在执行期间,它还会自动追踪 todoId.value 作为依赖(和计算属性类似)。每当 todoId.value 变化时,回调会再次执行。

虽然对于这种依赖项只有一个得例子来说,watchEffect的作用不是很明显,但如果有许多依赖项,且数据源不明确时呢,炒鸡好用!!

所以:watch 和 watchEffect 的主要区别其实就是追踪响应式依赖的方式不同而已。


7. 暂停、恢复和停止侦听器

<template>
  <el-card>
    <p>num: {{ count.num1 }}</p>
    <el-button @click="count.num1++">add</el-button>
    <el-button @click="pause">暂停</el-button>
    <el-button @click="resume">继续监听</el-button>
    <el-button @click="stop">停止监听</el-button>
  </el-card>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue'

const count = reactive({
  num1: 1,
})

const { stop, resume, pause } = watch(
  () => count.num1,
  (newNum, oldNum) => {
    console.log(oldNum, newNum)
  },
)
</script>

注意:仅支持 vue 3.5 及以上版本 

官方文档地址:响应式 API:核心 | Vue.js (vuejs.org)


8. 清理副作用

侦听器会在数据源变化时执行一些“副作用”。但这些“副作用”可能会产生一些需要后续清理的资源(如未完成的异步请求、事件监听器等),Vue提供了机制来清理这些资源,以避免内存泄漏或其他潜在问题。

1. watch

Vue 3.5之前,watch的第三个参数是一个可选的副作用清理函数。这个函数会在侦听器被停用或组件卸载时调用,允许你进行资源清理。

watch(id, async (newId, oldId, onCleanup) => {
  const { response, cancel } = doAsyncWork(newId)
  // 当 `id` 变化时,`cancel` 将被调用,
  // 取消之前的未完成的请求
  onCleanup(cancel)
  data.value = await response
})

Vue 3.5+

import { onWatcherCleanup } from 'vue'

watch(id, async (newId) => {
  const { response, cancel } = doAsyncWork(newId)
  onWatcherCleanup(cancel)
  data.value = await response
})

2. watchEffect

Vue 3.5之前, watchEffect 的第一个参数为副作用清理函数.

watchEffect(async (onCleanup) => {
  const { response, cancel } = doAsyncWork(newId)
  // 如果 `id` 变化,则调用 `cancel`,
  // 如果之前的请求未完成,则取消该请求
  onCleanup(cancel)
  data.value = await response
})

Vue 3.5+

import { onWatcherCleanup } from 'vue'

watchEffect(async () => {
  const { response, cancel } = doAsyncWork(newId)
  // 如果 `id` 变化,则调用 `cancel`,
  // 如果之前的请求未完成,则取消该请求
  onWatcherCleanup(cancel)
  data.value = await response
})

注意:onWatcherCleanup 仅在 Vue 3.5+ 中支持,并且必须在 watchEffect 效果函数或 watch 回调函数的同步执行期间调用:不能在异步函数的 await 语句之后调用它。


若有错误或描述不当的地方,烦请评论或私信指正,万分感谢 😃

如果我的文章对你有帮助,你的赞👍就是对我的最大支持^_^

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

沉浸式学习新体验:3D虚拟展厅如何重塑教育格局!

3D虚拟展厅对于教育行业产生了深远的影响&#xff0c;主要体现在以下几个方面&#xff1a; 一、创新教学方式 3D虚拟展厅利用三维技术构建的虚拟展示空间&#xff0c;为教育行业带来了一种全新的教学方式。传统的教学方式往往局限于书本和课堂&#xff0c;而3D虚拟展厅则能够…

【Kafka】Windows+KRaft部署指南

【Kafka】WindowsKRaft部署指南 摘要本地环境说明官网快速开始修改config/kraft/server.properties初始化数据存储目录启动 测试创建topic创建生产者创建消费者 FAQ输入行太长。命令语法不正确。问题描述解决方案 参考资料 摘要 Kafka是一种高吞吐量的分布式发布订阅消息系统&…

面相小白的php反序列化漏洞原理剖析

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文整理反序列化漏洞的一些成因原理 建议学习反序列化之前 先对php基础语法与面向对象有个大体的了解 (我觉得我整理的比较细致&#xff0c;了解这俩是个啥就行) 漏洞实战情况 这个漏洞黑盒几乎不会被发现&am…

景联文科技专业数据标注公司:高质量数据标注推动AI产业发展

在当今数据驱动的时代&#xff0c;高质量的数据标注对于机器学习、自然语言处理&#xff08;NLP&#xff09;和计算机视觉等技术领域的发展起着至关重要的作用。 数据标注是指对原始数据进行处理&#xff0c;标记对象的特征&#xff0c;生成满足机器学习训练要求的可读数据编码…

yelp数据集上识别潜在的热门商家

yelp数据集是研究B2C业态的一个很好的数据集&#xff0c;要识别潜在的热门商家是一个多维度的分析过程&#xff0c;涉及用户行为、商家特征和社区结构等多个因素。从yelp数据集里我们可以挖掘到下面信息有助于识别热门商家 用户评分和评论分析 评分均值: 商家的平均评分是反映其…

YOLO11改进 | 融合改进 | C3k2融合ContextGuided 【独家改进, 两种方式】

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 本文给大家带来的教程是将YOLO11的C3k2替…

【harbor】离线安装2.9.0-arm64架构服务制作和升级部署

harbor官网地址&#xff1a;Harbor 参考文档可以看这里&#xff1a;部署 harbor 2.10.1 arm64 - 简书。 前提环境准备&#xff1a; 安装docker 和 docker-compose 先拉arm64架构的harbor相关镜像 docker pull --platformlinux/arm64 ghcr.io/octohelm/harbor/harbor-regist…

InfluxDB 2 关闭pprof

背景&#xff1a; Go 语言的 net/http/pprgf包如未配置正确暴露在公网容易引起敏感信息泄漏问题&#xff0c;导致源码等信息泄漏。 influxdb 2 默认是开启pprof的 使用 localhost:8086/debug/pprof/goroutine?debug1 可以看到接口暴露的信息 如何关闭pprof 官方文档&…

CJ/T188-2004 详细介绍

REDISANT 提供互联网与物联网开发测试套件 # 互联网与中间件&#xff1a; Redis AssistantZooKeeper AssistantKafka AssistantRocketMQ AssistantRabbitMQ AssistantPulsar AssistantHBase AssistantNoSql AssistantEtcd AssistantGarnet Assistant 工业与物联网&#xff1…

阿里云k8s-master部署CNI网络插件遇到的问题

问题 按照网络上的部署方法 cd /opt/k8s # 下载 calico-kube-controllers配置文件&#xff0c;可能会网络超时 curl https://docs.projectcalico.org/manifests/calico.yaml -O kubectl apply -f calico.yaml 试了很多次都不行&#xff0c;k8s-master都是Not ready的状态 ca…

Netty篇(学习前言)

目录 一、为什么使用Netty 1. Netty编程相比NIO编程的优势 2. Netty 相比其它网络应用框架的优势 二、让我们走进Netty 1. 简介 2. 设计目标 3. 主要特点 4. Netty的作者 5. Netty 的地位 6. Netty 的优势 五、Netty版本说明 六、Netty架构设计 1. 线程模型基本介绍…

C/C++使用AddressSanitizer检测内存错误

AddressSanitizer 是一种内存错误检测工具&#xff0c;编译时添加 -fsanitizeaddress 选项可以在运行时检测出非法内存访问&#xff0c;当发生段错误时&#xff0c;AddressSanitizer 会输出详细的错误报告&#xff0c;包括出错位置的代码行号和调用栈&#xff0c;有助于快速定位…

JavaScript基础语法部分-黑马跟课笔记

一、Javascript介绍 1.JavaScript是什么&#xff1f; 1.是什么&#xff1f; 是一种运行在客户端&#xff08;浏览器&#xff09;的编程语言&#xff0c;实现人机交互效果 2.作用&#xff08;做什么&#xff1f;&#xff09; 网页特效&#xff08;监听用户的一些行为让网页做…

【MongoDB】MongoDB的Java API及Spring集成(Spring Data)

文章目录 Java APISpring 集成1. 添加依赖2. 配置 MongoDB3. 创建实体类4. 创建 Repository 接口5. 创建 Service 类6. 创建 Controller 类7. 启动 Spring Boot 应用8. 测试你的 API 更多相关内容可查看 Java API maven <dependency><groupId>org.mongodb</gr…

非线性关卡设计

【GDC】如何设计完全非线性的单人关卡_DOOM (bilibili.com) 本文章算是此视频的简单笔记&#xff0c;更详细还请看视频 设计完全非线性关卡强调自由移动和沙盒式玩法&#xff0c;鼓励玩家进行不可预测的移动和空间探索。讲解者分享了设计此类关卡的具体步骤&#xff0c;包括明…

(蓝桥杯C/C++)——基础算法(下)

目录 一、时空复杂度 1.时间复杂度 2.空间复杂度 3.分析技巧 4.代码示例 二、递归 1.递归的介绍 2.递归如何实现 3.递归和循环的比较 4.代码示例 三、差分 1.差分的原理和特点 2.差分的实现 3.例题讲解 四、枚举 1.枚举算法介绍 2.解空间的类型 3. 循环枚举解…

神经网络基础--什么是正向传播??什么是方向传播??

前言 本专栏更新神经网络的一些基础知识&#xff1b;这个是本人初学神经网络做的笔记&#xff0c;仅仅堆正向传播、方向传播就行了了一个讲解&#xff0c;更加系统的讲解&#xff0c;本人后面会更新《李沐动手学习深度学习》&#xff0c;会更有详细讲解;案例代码基于pytorch&a…

代码随想录算法训练营第三十七天 | 完全背包 518.零钱兑换 Ⅱ 377.组合总和Ⅳ 70.爬楼梯(进阶版)

完全背包&#xff1a; 文章链接 题目链接&#xff1a;卡码网 52.携带研究材料 与01背包的区别在于物品数量无限&#xff0c;因此同一种物品可以取多次。 递推式如下&#xff1a; 二维&#xff1a;dp[i][j] max(dp[i - 1][j], dp[i][j - weights[i]] value[i])&#xff0c;因…

C语言心型代码解析

方法一 心型极坐标方程 爱心代码你真的理解吗 笛卡尔的心型公式&#xff1a; for (y 1.5; y > -1.5; y - 0.1) for (x -1.5; x < 1.5; x 0.05) 代码里面用了二个for循环&#xff0c;第一个代表y轴&#xff0c;第二个代表x轴 二个增加的单位不同&#xff0c;能使得…

C语言网络编程 -- TCP/iP协议

一、Socket简介 1.1 什么是socket socket通常也称作"套接字"&#xff0c;⽤于描述IP地址和端⼝&#xff0c;是⼀个通信链的句柄&#xff0c;应⽤ 程序通常通过"套接字"向⽹络发出请求或者应答⽹络请求。⽹络通信就是两个进程 间的通信&#xff0c;这两个进…