vue watch 侦听器的权衡.md
# vue watch 侦听器的权衡
> 记录实习时一次发生的bug
在 Vue 3 中,`watch` 对数组、对象等‘引用类型’默认只比较**引用地址**:
```js
watch(
() => props.list,
(val) => {
// TODO
}
)
```
对 list 的监听只是地址,如:当父组件用 `splice` 原地修改数组时:
> splice:在当前数组的基础上进行更改
>
> 文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
```js
// 父组件
list.value.splice(idx, 1) // 原地删除,引用不变
```
子组件的 `watch` **不会触发** ——地址没变,不会触发更改。子组件仍内部保留旧数据,存在潜在的 bug
## 修复
#### 方案一:深层侦听 `{ deep: true }`
```js
watch(
() => props.list,
(val) => {
// TODO
},
{ deep: true } // 深度侦听
)
```
优缺:
- ✅ 能解决问题
- ❌ 性能开销:递归遍历每个属性建立依赖,每次嵌套值变化都触发
- ❌ 语义错位:若只关心增删,但深层侦听会对每个状态的变化进行侦听,如: `item.status` 变化也要触发一次深拷贝,因此若只考虑增删,使用 deep 开销太大
#### 方案二: filter 返回新引用
filter 会返回新的数组,此时地址发生了改变
```js
// 父组件
list.value = list.value.filter((_, i) => i !== idx) // 新数组,新引用
```
- ✅ 地址变了 →watch 自然触发
若只是对增删进行处理,建议将 `splice` 替换为`filter` ,只用一行代码,避免了 `deep: true` 带来的所有副作用
评论
?
参与讨论