bug: 使用watchEffect
时,方法被多次调用
watch
vs watchEffect
的区别
特性 | watch | watchEffect |
---|---|---|
使用方式 | 监听特定的响应式数据变化(ref、reactive 的属性) | 自动收集依赖,响应依赖项变化时自动重新执行回调 |
精确性 | 更加精确,只监听指定的响应式源 | 响应式依赖自动收集,容易造成过度监听 |
参数传入 | (newValue, oldValue) | 没有直接参数 |
控制能力 | 可以通过 immediate 、deep 、flush 等选项精准控制 | 缺乏对旧值、新值、深度监听等精确控制 |
推荐场景 | 精准监听某些响应式变量的变化,如表单变化、状态变化等 | 快速原型开发、调试中用于临时打印数据 |
副作用管理 | 推荐用于执行副作用(如请求、日志、表单校验) | 不推荐用于副作用逻辑 |
watch
的典型用法与参数说明
import { ref, watch } from 'vue'
const count = ref(0)
watch(
count,
(newVal, oldVal) => {
console.log('new:', newVal, 'old:', oldVal)
},
{
immediate: true, // 立即执行一次
deep: false, // 是否深度监听(对对象或数组等嵌套结构有效)
flush: 'post', // 回调触发的时机('pre'、'post'、'sync')
}
)
参数说明
-
immediate
: 默认是false
,如果为true
,会在监听开始时立即执行一次回调。immediate: true
用于需要初始执行一次逻辑的场景(例如初始加载数据)。
-
deep
: 默认是false
。当监听的是一个对象或数组时,设置为true
可以深度监听内部变化。deep: true
-
flush
: 控制回调执行的时机:-
'pre'
: DOM 更新前(默认) -
'post'
: DOM 更新后 -
'sync'
: 同步执行
-
oldValue
和 newValue
的用法
watch
的回调中会提供两个参数:
watch(
someRef,
(newVal, oldVal) => {
console.log('值变化了:', oldVal, '->', newVal)
}
)
常用于:
-
判断前后值是否有本质变化(避免重复请求)
-
回滚操作(如表单数据被错误修改)
-
依赖旧值进行更新逻辑(如分页切换时保存上一个页码)
为什么不推荐使用 watchEffect
进行副作用操作?
import { watchEffect } from 'vue'
watchEffect(() => {
console.log('当前值为:', someRef.value)
fetchData(someRef.value) // 不推荐这样写副作用!
})
原因如下:
-
依赖不明确
watchEffect
自动收集依赖,开发者难以控制依赖边界,可能导致非预期的多次执行。 -
副作用频繁触发 所有依赖一旦变化就会重新执行,即使变化无关紧要,也会重复调用副作用逻辑(如网络请求)。
-
无法访问旧值 相较于
watch
,watchEffect
无法拿到oldValue
,难以做“增量式更新”或“比较前后”的逻辑判断。 -
难以调试和维护 自动收集依赖导致逻辑混乱,后期维护困难,尤其是在复杂业务场景中。
适合场景(推荐):
-
快速原型调试
-
简单的计算、打印逻辑
-
响应式日志或测试环境下的变量追踪
总结
项目 | watch | watchEffect |
---|---|---|
依赖收集 | 手动指定 | 自动收集 |
是否能获取旧值 | oldValue 支持 | 不支持 |
是否适合副作用 | 适合精确副作用逻辑 | 不推荐用于副作用 |
精细控制 | immediate、deep、flush 等控制 | 缺乏控制 |
推荐场景 | 表单监听、API 请求、状态同步等 | 简单数据跟踪、调试打印等 |
computed
和 watch
的区别一览
特性 | computed 计算属性 | watch 侦听器 |
---|---|---|
作用 | 根据依赖自动计算并返回一个值 | 监听数据变化,执行副作用(如异步操作) |
返回值 | 返回一个值 | 不返回值,只执行回调函数 |
缓存 | 有缓存,依赖不变则不重新计算 | 无缓存,依赖变就执行 |
用于展示 | 适合用于模板或 UI 展示 | 不适合直接用于展示 |
是否适合副作用 | 不适合 | 专门用于副作用操作(如 API 请求) |
能否访问旧值 | 无 | 可以拿到 oldValue 和 newValue |
computed
的典型使用
import { ref, computed } from 'vue'
const firstName = ref('Tao')
const lastName = ref('Yao')
// 计算全名
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
// 也可以有 setter(双向绑定)
const reversed = computed({
get: () => fullName.value.split('').reverse().join(''),
set: (val) => {
// 通常不推荐这么用,仅演示写法
fullName.value = val.split('').reverse().join('')
}
})
特点:
-
响应式:依赖的响应式数据改变时,自动更新。
-
有缓存:如果依赖没变,重复访问不会重新计算。
-
常用于模板展示、计算逻辑。
使用建议总结
场景 | 推荐使用方式 | 说明 |
---|---|---|
模板展示、纯值计算 | computed | 例如:金额格式化、过滤已完成任务、拼接姓名等 |
数据变化后需执行副作用(请求等) | watch | 如:用户输入搜索词,请求后台接口 |
需要访问旧值以做比较处理 | watch | 如:当值变化时记录日志,或者判断是否真正发生变化 |
依赖多个数据源组合出一个新值 | computed | 计算属性能自动管理多个依赖的组合逻辑 |
想要延迟执行逻辑(防抖、节流) | watch + lodash.debounce | computed 无法处理副作用延迟逻辑 |
想要响应式地观察整个对象 | watch(obj, cb, { deep: true }) | computed 无法观察对象内部变化 |
错误使用示例提醒
// ❌ computed 中做副作用(不推荐!)
const wrong = computed(() => {
fetch('/api/data') // 不应在 computed 中做请求
return 123
})
副作用请始终用 watch
,computed
只用于值的“推导”。
小结一句话口诀
computed
管值、watch
管事,展示选 computed,操作选 watch。