Vue3 零基础每日笔记(009):watch 侦听器——数据一变就“做事“

一、前言

上篇 computed 管"算值",但它禁止干杂活。实际开发中大量需求是"数据一变,就去做某件事":搜索词一变就发请求、配置一变就存 localStorage、路由一变就刷新列表。这些"做事"的场景,主角就是 watch 侦听器。

二、基础用法:监听一个 ref

watch(要监听的数据, (新值, 旧值) => { 做事 })

vue 复制代码
<template>
  <p>计数:{{ count }}</p>
  <button @click="count++">+1</button>
</template>

<script setup>
import { ref, watch } from 'vue'

const count = ref(0)

// 数据一变,回调立刻执行
watch(count, (newVal, oldVal) => {
  console.log(`从 ${oldVal} 变成了 ${newVal}`)
  // 这里可以:发请求、存本地、弹提示......干一切"副作用"
})
</script>

点三次按钮,控制台依次输出:从 0 变成了 1从 1 变成了 2从 2 变成了 3

注意和 computed 的分工:watch 的回调不需要 return 值,它不为页面提供数据,纯粹是"听到变化后去执行的动作"。

三、监听的正确姿势(新手最容易写错的地方)

1. 监听 ref 包的基本类型:直接传名字

js 复制代码
watch(count, cb)  // ✅
watch(count.value, cb)  // ❌ 传了个死值 0,永远监听不到变化

2. 监听 reactive 对象的单个属性:必须用 getter 函数

js 复制代码
const user = reactive({ name: '小明', age: 18 })

watch(() => user.age, (n, o) => {
  console.log(`年龄 ${o} → ${n}`)
})  // ✅ 传一个"返回该属性"的箭头函数

watch(user.age, cb)  // ❌ 传的是 18 这个死值,还伴随警告

3. 监听整个 reactive 对象:直接传,自动深度监听

js 复制代码
watch(user, (n) => {
  console.log('user 里有任何属性变了', n)
}, { deep: true })  // 传 reactive 对象时 deep 写不写都深层监听

4. 监听 ref 包的对象:改内部属性默认不触发,要加 deep

js 复制代码
const data = ref({ list: [1, 2] })

watch(data, cb)  // 改 data.value.list 只能听个寂寞
watch(data, cb, { deep: true })  // ✅ 深层属性变化也能听到

四、多数据源与两个常用选项

同时监听多个数据,源写成数组,回调参数也是数组:

js 复制代码
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
  console.log('姓或名变了', newFirst, newLast)
})

immediate:进页面立刻执行一次

默认数据"变了才执行",但有的场景初始化就要跑一次(比如进页面先按默认关键词查一次):

js 复制代码
watch(keyword, fetchData, { immediate: true })  // 创建时先执行一次

deep:深度监听

上面第三、四节的场景。提醒:deep 会递归遍历所有层级,超大对象慎用,性能篇会给替代方案。

五、综合实战:搜索联想(发请求版)

真实项目里 watch 出镜率最高的场景------输入关键词请求接口:

vue 复制代码
<template>
  <input v-model="keyword" placeholder="输入关键词搜索" />
  <p v-if="loading">🔍 搜索中...</p>
  <ul v-else>
    <li v-for="item in result" :key="item">{{ item }}</li>
  </ul>
</template>

<script setup>
import { ref, watch } from 'vue'

const keyword = ref('')
const loading = ref(false)
const result = ref([])

// 模拟的接口
function fetchSearch(kw) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(['Vue3 ' + kw, 'Vite ' + kw, kw + ' 教程'])
    }, 500)
  })
}

watch(keyword, async (newKw) => {
  if (!newKw) {
    result.value = []
    return
  }
  loading.value = true
  result.value = await fetchSearch(newKw)
  loading.value = false
})
</script>

每敲一个字自动重新搜索,loading 状态自动切换------数据驱动的搜索,一行 DOM 操作都没写。

【截图位置:输入"教程"后搜索中与结果列表的运行效果】

小瑕疵留给你思考:连续快速输入会发一堆请求,怎么只发最后一次?(防抖,012 篇和性能篇都会再遇到它)

六、踩坑记录

  1. watch(count.value, cb) :传了死值,监听无效------记住"传引用不传值",对象属性用 getter 函数 () => obj.x
  2. 监听整个 reactive 时新旧值相同newVal === oldVal,因为是同一个对象引用。要拿到"哪变了",监听具体属性
  3. 回调里又改了被监听的数据:无限循环警告"Maximum recursive updates exceeded"------回调里别改自己正在监听的
  4. 进页面不执行 :想要初始化也执行一次,immediate: true
  5. 组件卸载忘停止 :在 setup 里写的 watch 会随组件卸载自动停止,放心用;但如果是在 setTimeout/手动回调里创建的 watch,必须手动接住返回值 const stop = watch(...),需要时 stop()

七、今日小结

  • watch = 监听数据变化,执行副作用(发请求/存本地/弹提示)
  • 四种监听姿势:ref 直接传、对象属性用 getter、reactive 自动深层、ref 包对象要 deep
  • immediate 初始化即执行,deep 深层监听,多数据源传数组
  • computed 算值有缓存,watch 做事没缓存,各司其职

作者:陈志宝

下篇预告

watch 还要手动指定"听谁",有个更懒的兄弟 watchEffect:回调里用到谁就自动听谁,还自带"立即执行"。下一篇讲透它与 watch 的区别和分工。

相关推荐
一夜枫林1 小时前
vue实现scroll-view上下滑动右侧左右滑动
前端·javascript·vue.js
噜~噜~噜~1 小时前
操作系统笔记-3.1.1.4 (选修)覆盖与交换
笔记·操作系统
志尊宝1 小时前
Vue3 零基础每日笔记(011):生命周期钩子——在正确的时间做正确的事
javascript·vue.js·笔记
prog_61032 小时前
【笔记】用cursor手搓cursor(十)
人工智能·笔记·大语言模型·agent
卤蛋fg62 小时前
vue 甘特图组件 vxe-gantt 依赖连接线可视化拖拽创建与双击删除
vue.js
浔溺3 小时前
al+大数据每日学习笔记36
大数据·笔记·学习
浔溺4 小时前
al+大数据每日学习笔记35
大数据·笔记·学习
WeiXin_DZbishe4 小时前
基于springboot大学生提问箱系统-计算机毕设【课程设计】72593
javascript·vue.js·spring boot·vscode·python·node.js·php
kyrie_sakura4 小时前
MySQL学习笔记4 -- select的7大子句,子查询
笔记·学习·mysql