You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue多响应式值场景下watch函数的正确用法及原理探究

Vue中watch未监听所有用到的响应式变量的正确用法及原理

一、React与Vue的依赖处理差异

在React中使用useEffect时,若回调内用到的响应式变量未加入依赖数组,会触发ESLint的exhaustive-deps错误:

const [foo, setFoo] = useState("foo")
const [bar, setBar] = useState("bar")

useEffect(() => {
  if (foo === 'something' && bar !== 'anything') {
    console.log({ foo, bar })
  }
}, [foo]) // Error: react-hooks/exhaustive-deps

但在Vue中,以下写法不会报错:

const foo = ref('foo');
const bar = ref('bar');

watch(foo, (newFoo) => {
  if (bar.value !== 'bar') {
    console.log(newFoo)
  }
})

我曾认为应该监听所有用到的响应式变量,写法如下:

const foo = ref('foo');
const bar = ref('bar');

watch([foo, bar], ([newFoo, newBar]) => {
  if (newBar !== 'bar') {
    console.log(newFoo)
  }
})

请问该场景下watch的正确用法是什么?背后原理为何?

二、测试代码

为验证两种写法的差异,我编写了以下测试代码:

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

const foo = ref('foo')
const bar = ref('bar')
const fooChecked = ref(false)
const isBarNotBar = ref(false)

const foo2 = ref('foo2')
const bar2 = ref('bar2')
const foo2Checked = ref(false)
const isBar2NotBar = ref(false)

// 仅监听foo的写法
watch(foo, (newFoo) => {
  fooChecked.value = true
  if (bar.value !== 'bar') {
    isBarNotBar.value = true
  }
})
  
// 监听foo和bar的写法
watch([foo2, bar2], ([newFoo2, newBar2]) => {
  foo2Checked.value = true
  if (newBar2 !== 'bar') {
    isBar2NotBar.value = true
  }
})
  
// 异步修改bar的方法
function changeBarAsync(){
  setTimeout(() => {
    bar.value = 'newBar'
  }, 0)
}
  
function changeBar2Async(){
  setTimeout(() => {
    bar2.value = 'newBar'
  }, 0)
}
</script>

<template>
    <div>
    {{ foo }} | {{ bar }}
    <div :style="isBarNotBar ? { color: 'red' } : { color: 'green' }">
      {{ isBarNotBar ? 'bar is not bar' : 'bar is bar' }}{{ fooChecked ? ' ✅' : '' }}
    </div>
  </div>
  <br>
  <button @click="() => foo = 'newFoo'">
    change foo
  </button>
  <button @click="() => { foo = 'newFoo'; bar = 'newBar'}">
    change foo and bar
  </button>
  <button @click="() => { foo = 'newFoo'; changeBarAsync(); }">
    change foo and bar async
  </button>
  <br >
  <br >
  <div :style="{ width: '100%', height: '1px', backgroundColor: '#777' }"></div>
  <br >
  <div>
    {{ foo2 }} | {{ bar2 }}
    <div :style="isBar2NotBar ? { color: 'red' } : { color: 'green' }">
      {{ isBar2NotBar ? 'bar is not bar' : 'bar is bar' }}{{ foo2Checked ? ' ✅' : '' }}
    </div>
  </div>
  <br>
  <button @click="() => foo2 = 'newFoo'">
    change foo2
  </button>
  <button @click="() => { foo2 = 'newFoo'; bar2 = 'newBar'}">
    change foo2 and bar2
  </button>
  <button @click="() => { foo2 = 'newFoo'; changeBar2Async(); }">
    change foo2 and bar2 async
  </button>
  <br >
</template>

三、正确用法及原理

核心结论

两种写法都合法,关键取决于你的业务触发逻辑需求:

1. 仅监听特定变量的写法(单源监听)

如果你的需求是:只有当foo发生变化时,才执行回调逻辑并检查bar的当前值,那第一种写法完全没问题。

  • 原理:Vue的响应式系统基于Proxy/Object.defineProperty,回调执行时会直接读取bar.value的最新值,不存在React中闭包捕获旧值的问题。
  • 局限性:如果bar单独发生变化,这个watch不会触发——这是符合预期的,因为你只监听了foo的变化。

2. 监听多个变量的写法(多源监听)

如果你的需求是:只要foo或bar任意一个发生变化,都要执行回调逻辑,那必须使用第二种写法,将所有相关变量加入监听数组。

  • 原理:Vue的watch支持监听数组形式的数据源,当数组中任意一个值变化时,都会触发回调,回调参数会返回所有数据源的最新值。
  • 适用场景:比如你的逻辑依赖foo和bar的组合状态,任何一个变量变化都需要重新判断逻辑。

测试场景的差异说明

在测试代码的"change foo and bar async"场景中:

  • 单源监听写法:foo先变化触发watch,此时bar还未完成异步修改,所以isBarNotBar保持false;之后bar异步变化,但因为没被监听,watch不会再次触发,isBarNotBar始终为false。
  • 多源监听写法:foo变化时触发一次watch,之后bar异步变化时会再次触发watch,此时读取到bar2的最新值,isBar2NotBar会变为true。

内容的提问来源于stack exchange,提问作者Bo Hyeon Seo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 04:15:38