用好了 watchEffect 才算会用 Vue3 —— 那些让人误解的响应式陷阱

很多 Vue 3 用户都知道有 watchEffect(),也偶尔用用,但真正理解它运行机制和使用场景的开发者,其实并不多。

它被认为是"比 watch 更方便的响应式侦听",但用不好,它也能成为项目中最隐蔽的性能杀手

这篇文章,我们就来一次彻底的深潜 ------ 你以为你会用 watchEffect,其实你只是在用它"运气好"。


一、watchEffect() 到底是个什么东西?

一句话概括:

watchEffect()立即运行传入的函数,并自动追踪其中用到的响应式依赖,一旦依赖变了,就重新运行这个函数。

来看个例子:

ts 复制代码
const count = ref(0)

watchEffect(() => {
  console.log('count 的值是', count.value)
})

这段代码会:

  • 马上执行一次,打印出 0;
  • 每次 count.value 改变时,都会再次执行并打印。

你甚至不需要手动指定 count 是依赖,Vue 会自动"收集依赖"。


二、和 watch() 有啥区别?

这也是 80% 的 Vue 用户最常犯的概念混淆:

特性 watchEffect watch
是否立即执行? 否(除非 immediate: true
是否自动收集依赖? 否(你要手动指定依赖)
是否可获取旧值? 是((newVal, oldVal) => {}
更适合哪种场景? 响应式副作用(如:操作 DOM、console.log、发请求) 监听特定变量的变化,并对比旧新值

结论:

  • watchEffect 是"立即执行 + 自动依赖追踪"
  • watch 是"精准监听 + 需要手动指定依赖 + 能拿旧值"

三、用 watchEffect 容易踩的 5 个坑

1. 副作用代码"无限循环"

ts 复制代码
const count = ref(0)

watchEffect(() => {
  count.value++  // ❌ 修改了自身依赖,会死循环!
})

死循环就这么来了。

✅ 正确写法是:只读取响应式数据,不要在 watchEffect 里直接修改它们。


2. 错误依赖:隐式变量没收集到

ts 复制代码
let localCount = 0

watchEffect(() => {
  console.log(localCount) // ❌ localCount 不是响应式的,watchEffect 不追踪它
})

要注意:非响应式变量不会被追踪,就算你用到了也不会触发重新运行。


3. 嵌套条件分支导致依赖不完整

ts 复制代码
const a = ref(1)
const b = ref(2)

watchEffect(() => {
  if (a.value > 0) {
    console.log(b.value)  // ❗ 只有在 a > 0 时才会追踪 b
  }
})

a <= 0 时,b 根本没被追踪。下次即使 b 变了,也不会重新执行 watchEffect。这很容易导致逻辑错乱或"触发不了"的问题

✅ 解决方案:让依赖收集"稳定",避免在分支中读取响应式变量。


4. 你以为收集了依赖,其实没有

ts 复制代码
const user = ref({ name: '张三' })

watchEffect(() => {
  console.log(user) // ❌ 打印的是 ref 对象本身,没有触发 getter,Vue 无法追踪
})

访问的是 ref 对象本身,而不是它的 .value,Vue 不会收集依赖。

✅ 改为 console.log(user.value.name),才能触发依赖收集。


5. 你没清理副作用,留下了"内存幽灵"

ts 复制代码
watchEffect(() => {
  const el = document.getElementById('app')
  el?.addEventListener('click', handleClick)
})

每次依赖变化就会重新运行,但你没有移除旧的事件监听器。这就产生了堆积副作用。

✅ 正确写法是使用 onInvalidate() 来做清理:

ts 复制代码
watchEffect((onInvalidate) => {
  const el = document.getElementById('app')
  el?.addEventListener('click', handleClick)

  onInvalidate(() => {
    el?.removeEventListener('click', handleClick)
  })
})

四、什么时候该用 watchEffect,什么时候该用 watch

watchEffect 的适用场景:

  • 自动追踪依赖的副作用逻辑,比如:

    • 页面标题:document.title = xxx
    • 打日志:console.log()
    • 响应式触发 API 调用(但要小心节流)
  • 生命周期中自动执行的响应式行为

watch 的适用场景:

  • 明确指定监听哪个值
  • 需要对比旧值和新值
  • 监听嵌套数据(用 deep: true)
  • 不想立即执行,只在变化时触发

五、一些高级技巧:让 watchEffect 更丝滑

✅ 和 async/await 一起用

ts 复制代码
watchEffect(async () => {
  const res = await fetchUser()
  data.value = res
})

注意:不要在里面直接用非响应式的中间变量,会丢依赖。


✅ 搭配组件卸载自动清理

在 Vue 3 里,watchEffect自动在组件卸载时停止执行 ,无需手动停止。这一点比 Vue 2 的 watch 更方便。


✅ 你真的会用 watchEffect 吗?

很多人只是用它"凑合",但真正用好了,你能获得:

  • 更清晰的副作用组织逻辑
  • 更少的手动依赖指定
  • 更丝滑的组件行为

而不是无限循环、依赖不生效、内存泄漏、控制不住副作用。

掌握 watchEffect,是迈入 Vue3 响应式编程的第一关。


📌 你可以继续看我的Vue系列文章

相关推荐
weixin_382395231 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__1 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.2 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~3 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华3 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子6 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅6 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni6 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学7 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端