Vue3 零基础每日笔记(010):watchEffect——用到谁就自动听谁的“懒人侦听器“

一、前言

watch 已经很强了,但每次都要先想清楚"监听谁",依赖一多 watch([a, b, c, d], ...) 写到眼花。今天的主角 watchEffect 更懒更聪明:回调里用到谁,就自动监听谁,还天生自带立即执行。学完这篇,watch 三兄弟(computed / watch / watchEffect)就全部集齐了。

二、基础用法:自动依赖收集

watchEffect 接收一个函数,立即执行一次,执行过程中用到哪些响应式数据,就自动把哪些加入监听:

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

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

const count = ref(0)
const double = computed(() => count.value * 2)

watchEffect(() => {
  console.log(`count=${count.value}, double=${double.value}`)
})
</script>

运行效果:

  • 创建时立刻执行一次 (输出 count=0, double=0)------不需要 watch 的 immediate: true
  • 点按钮改 count:自动重新执行------因为回调里"用到了" count

对比一下手动写法:

js 复制代码
// watch 版:要手动声明监听 count
watch(count, () => {
  console.log(`count=${count.value}, double=${double.value}`)
}, { immediate: true })

// watchEffect 版:用到谁听谁,依赖不用写
watchEffect(() => {
  console.log(`count=${count.value}, double=${double.value}`)
})

依赖少没感觉,依赖五六个的时候,watchEffect 的省心程度直线上升。

三、watch 与 watchEffect 怎么选

不是替代关系,是分工关系,一张表看明白:

对比项 watch watchEffect
依赖 手动显式声明 自动收集(用到谁听谁)
首次执行 默认不执行,要 immediate ✅ 创建即执行
新旧值 ✅ 回调里有 (newVal, oldVal) ❌ 没有
执行时机 数据变化后触发 创建即跑 + 依赖变化再跑
适合场景 需要知道"从几变到几"、按条件监听 依赖多个状态的副作用

选型口诀:

  • 要旧值 / 要条件判断监听谁 → watch
  • 一进来就要跑一次 + 依赖一堆状态 → watchEffect

比如"关键词变化后发请求"两者都能写,但"从上一页变成当前页时滚回顶部"(需要旧值)就必须 watch。

四、停止侦听与清理副作用

停止侦听:接收返回值,调用即停:

js 复制代码
const stop = watchEffect(() => {
  console.log('count 变了', count.value)
})

// 3 秒后不再监听
setTimeout(() => stop(), 3000)

写在 <script setup> 里时,组件卸载会自动停止,一般不用手动管。

清理副作用(onCleanup):每次重新执行前,先执行上一次注册的清理函数------经典场景是"取消上一次还没回来的请求",防止旧结果覆盖新结果:

js 复制代码
watchEffect(async (onCleanup) => {
  const kw = keyword.value
  const controller = new AbortController()

  onCleanup(() => controller.abort())  // 下一次执行前,掐断上一次请求

  const res = await fetch('/api/search?kw=' + kw, { signal: controller.signal })
})

快速连续输入时,过期请求会被自动取消,只有最后一次的结果能落到页面------竞态问题一步到位。

五、综合实战:草稿自动保存

写博客最怕浏览器崩溃,用 watchEffect 做"边写边存":

vue 复制代码
<template>
  <input v-model="draft.title" placeholder="标题" />
  <textarea v-model="draft.content" rows="8" placeholder="正文"></textarea>
  <p>✅ 已自动保存 {{ savedCount }} 次</p>
  <button @click="clear">清空</button>
</template>

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

const draft = reactive({
  title: localStorage.getItem('draft_title') || '',
  content: localStorage.getItem('draft_content') || ''
})
const savedCount = ref(0)

// 标题或正文任何一个变了,都自动存一次
watchEffect(() => {
  localStorage.setItem('draft_title', draft.title)
  localStorage.setItem('draft_content', draft.content)
  savedCount.value++
})

function clear() {
  draft.title = ''
  draft.content = ''
}
</script>

三个细节品味一下:

  1. 初始化时立即保存了一次(watchEffect 天生立即执行)------正好完成"刷新页面恢复草稿"
  2. 回调里用了 draft.titledraft.content两个都自动进了监听名单 ,不用手写 watch([() => draft.title, () => draft.content], ...)
  3. 刷新页面,草稿从 localStorage 自动恢复

【截图位置:输入内容后刷新页面,草稿自动恢复的运行效果】

六、踩坑记录

  1. 回调里"只想初始化跑一次"的逻辑会重复执行:watchEffect 依赖变了就重跑,初始化一次性逻辑(弹个提示、初始化地图)请放 onMounted(下一篇主角)

  2. 异步代码之后的依赖不会被收集

    js 复制代码
    watchEffect(async () => {
      const a = await getData()   // await 之前的依赖能收集
      console.log(a.value + count.value)  // ❌ count 在 await 之后,不进监听名单
    })

    依赖请尽量写在 await 之前,否则依赖变化不会重新触发。

  3. 不要在回调里修改自己依赖的数据watchEffect(() => count.value++) 直接死循环警告

  4. 需要新旧值时别硬用:watchEffect 没有 oldVal,硬要比较历史值就得自己存变量------不如直接用 watch

  5. 过度使用:回调里多写了一个无关的响应式数据,就会多一个触发源,"自动收集"是把双刃剑,注意别在回调里顺手用了不相干的状态

七、今日小结

  • watchEffect:立即执行 + 用到谁自动听谁,专注副作用
  • watch 有新旧值、显式依赖;watchEffect 自动收集、天生立即执行
  • stop() 手动停止,onCleanup 清理上一次副作用(防竞态)
  • 三兄弟分工:computed 算值、watch 精准监听、watchEffect 自动跑腿

作者:陈志宝

下篇预告

组件从"出生"到"销毁"有一整个人生,发请求要等"出生后"(onMounted),定时器要在"临终前"清掉(onBeforeUnmount)。下一篇生命周期钩子:在正确的时间做正确的事。

相关推荐
狗哥哥1 小时前
从“看对方向”到“做出行动”:投资决策卡
前端
拖孩1 小时前
一个人 + AI 做的小程序,一个月赚了 36 块
前端·后端·微信小程序
weixin_440730501 小时前
playwright实战-渠道应用操作
开发语言·前端·python
陈年老古董1 小时前
Python模拟MapReduce分治思想 | 从单文件统计到大文件拆分聚合 学习笔记
开发语言·hadoop·笔记·python·学习
lhldsg1 小时前
从零构建智慧场馆解决方案小程序:开发全流程实战
java·前端·小程序
IMPYLH1 小时前
HTML 的 <rp> 元素
前端·javascript·html
晴天161 小时前
npm install -f(--force)深度解析:作用原理、报错根源与风险避坑指南
前端·npm·node.js
snow@li1 小时前
服务器运维:Node.js 安装笔记(Alibaba Cloud Linux 4)
笔记
IT_陈寒1 小时前
React的状态更新竟然不是同步的?!坑了我一整天
前端·人工智能·后端