DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

Vue 3 数组 push、sort 后 watch 不触发

数组ref和getter默认观察替换,Vue3.5起deep:1可观察结构变化。区分直接reactive监听、深层字段以及oldValue并非快照。

数组经过 push()、splice() 或 sort(),页面列表已经变化,watch(items, callback) 却没有执行。先确认 items 的形态:监听一个数组 ref,或用 getter 返回数组,默认主要观察它被替换,不会因为内部结构变化就自动调用回调。

这与“Vue 没有追踪数组”不同。模板渲染和 watcher 的监听来源、深度各有规则;直接把 reactive 数组传给 watch,又属于隐式深层监听的另一种调用。

Vue 3.5 起可用 deep: 1 监听数组结构

如果需要观察数组替换、增加、删除及顺序变化,但不需要深入每个元素对象的全部字段,可在 Vue 3.5 及之后设置 deep: 1:

import { ref, watch } from 'vue';

const items = ref([{ id: 1, title: 'A' }]);

const stop = watch(items, (next, previous) => {
  // 处理当前数组结构;previous 不是历史深拷贝
  console.log(next.map(item => item.id));
}, { deep: 1 });

这段应放在合适的组件或作用域中。脱离组件自行建立的 watcher,需要按其生命周期调用 stop。deep: 1 不会监听 items.value[0].title 这样的元素内部字段变化;如果业务确实依赖 title,应增加对应监听来源或采用合适的更深层监听。

getter 写法同样需要选定深度:

watch(() => state.items, onItemsChanged, { deep: 1 });

这里 state 为已有的响应式对象,onItemsChanged 为业务回调。不要把它改成 watch(state.items.length, ...):传入当前数字不是可追踪的 getter。只监听长度时应使用 () => state.items.length,但 sort 不改变长度,不会因此被捕获。

旧版本可选深层监听或结构副本

Vue 3.0 至 3.4 没有数字深度选项,deep: true 会进一步监听元素中的嵌套变化。只需要结构变化时,也可以让 getter 返回浅副本:

watch(() => [...items.value], (next, previous) => {
  // 比较元素身份与顺序
});

浅副本保留本次元素的顺序和身份,不是元素内容的历史深拷贝。大型数组每次求值都需要遍历并分配数组,选择前要看监听目的和数据规模;不要为了统计数量复制整个数组。

newValue 和 oldValue 相同不代表没变化

深层监听捕获同一个数组的就地修改时,新旧参数可能仍指向同一数组。回调被调用,不意味着 Vue 替你保留了一份修改前快照。若要记录精确差异,可在业务变更入口记录操作,或者根据需要保存独立的前态,不能直接比较同一对象宣布没有变化。

相反,watch(reactiveArray, callback) 默认会隐式深层监听;与监听数组 ref 或 () => state.items 不同。修复前保留实际调用方式,不要给所有 watcher 统一加 deep。

分别检查替换数组、push、splice、sort,以及只改一个元素字段。确认回调覆盖的是业务需要的变化;默认调度可能将同一轮的多次修改合并,不能把调用次数直接当成操作次数,也不必为增加次数改用同步 watcher。