watch难点在于结构变化、首次触发、引用类型误判三类场景;需明确监听源类型、控制深度策略、主动处理边界值,如检查oldVal是否undefined、用computed聚合多变量、deep:true配合独立副本比对。

watch 获取新值与旧值本身不难,难点在于结构变化、首次触发、引用类型误判这三类场景下容易报错或对比失效。核心是明确监听源类型、控制深度策略、主动处理边界值。
监听 ref 或基本类型:直接解构,注意首次 oldVal 为 undefined
当监听的是 ref(如 const count = ref(0))或计算属性返回基本值时,回调中 newVal 和 oldVal 是真实值,可直接比较:
- 首次执行时
oldVal一定是undefined,做相等判断前必须加空值检查,例如:if (oldVal !== undefined && newVal !== oldVal) { ... } - 避免用
==或隐式转换,统一用!==防止0 == false类误判 - 若需区分“从无到有”和“值变更”,可单独判断
oldVal === undefined
监听对象或数组:必须用 computed 聚合 + deep: true,且新旧值需结构一致
直接监听 reactive 对象或未包装的 ref 对象,会导致 oldVal 和 newVal 指向同一引用(浅监听),或因未开启深度而漏触发。正确做法是:
- 用
computed显式构造一个只含关注字段的响应式对象,例如:computed(() => ({ a: obj.a, b: obj.b })) - watch 该 computed,并配置
{ deep: true }—— 此时新旧值才是独立副本,可安全逐字段比对 - 禁止监听整个大对象(如
watch(form, ...)),否则无关字段更新也会触发,且无法定位具体变更项
监听多个独立变量:别写多个 watch,统一收口到 computed
比如同时响应 keyword、status、page 三个 ref,不要分别写三个 watch,否则逻辑分散、重复判空、难以维护:
- 定义聚合源:
const searchParams = computed(() => ({ keyword: keyword.value, status: status.value, page: page.value })) - watch
searchParams,在回调中按字段判断变化,例如:if (newVal.keyword !== oldVal.keyword) { ... } - 这样既保证新旧值结构稳定,又天然支持“仅某字段变才执行对应逻辑”,避免冗余请求或渲染
安全对比技巧:字段级差异提取 + 防空兜底
实际业务中常需知道“哪些字段变了”,而不是笼统执行全量逻辑。推荐写法:
- 先确保
oldVal存在(oldVal && newVal),再遍历 key 对比 - 用
Object.keys(newVal)获取所有字段,逐个判断newVal[key] !== oldVal?.[key] - 对可能为对象/数组的字段,用
JSON.stringify粗略比对(仅限简单结构);复杂嵌套建议用 lodash 的isEqual或自定义深比较函数 - 始终给 watch 配置
immediate: false(默认值),除非明确需要初始触发;若需初始加载,改用 onMounted + 手动调用逻辑更可控

















