【Vue3】详细探究 watch ref 数组不生效的问题

结果

不废话,直接上结果:

方法arr.pusharr.splicearr[i] = …arr = …arr.length = 0
模板✔️✔️✔️✔️✔️
watch(arr) ✖️✖️✖️✔️✖️
watch(arr.value)✔️✔️✔️✖️✔️
watch(arr, { deep: true })✔️✔️✔️✔️✔️
watch(arr.value.length)⚠️⚠️⚠️⚠️⚠️
watch(() => arr.value)✖️✖️✖️✔️✖️
watch(() => arr.value.length)✔️✔️✖️✔️*✔️*

从结果来看,watch(arr, { deep: true }) 最为保险,与模板行为一致。但是 deep watch 太大的对象可能会影响性能。

你可以根据上面的表格来确定到底用哪一种方法。

说明:

  1. ✔️ 表示这一列的操作能触发这一行的 watch,✖️ 同理。
  2. ⚠️ 表示报错。
  3. 列头上的操作省去了 .value。因为是在模板中执行的代码,不用加 .value

(* 见下方解释)

解释

  1. watch(arr):监听的是 ref 的值,也就是数组的引用。只有整个数组被替换时才会触发。
  2. watch(arr.value)ref 中的非原始值底层会自动改用 reactive,因此 watch arr.value 相当于 watch 底层的 reactive 对象。当数组被整个替换后,watch 的依旧是原来的数组,而不是新的数组,所以 arr = ... 不会被触发。
  3. watch(arr, { deep: true }):相当于 1 和 2 的组合。
  4. watch(arr.value.length)arr.value.length 是个数字,不是 ref 对象、reactive 对象或者 getter 函数,因此这段代码根本无法执行。
  5. watch(() => arr.value):和 1 是一样的。这点在 Vue3 文档中也有提及
  6. watch(() => arr.value.length):4 的正确书写版。因为 watch 的是 arr 的长度,不管 arr 发生了什么,只要长度变了就会触发。测试中的 arr = ... 恰好新旧数组长度不一致,所以可以触发。

测试代码

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

const counter = ref(0);

const arr = ref([]);
const _watch = ref([]);
const _watchValue = ref([]);
const _watchDeep = ref([]);
const _watchLength = ref('[Vue warn]: Invalid watch source:  0 A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types. ');
const _watchGetter = ref([]);
const _watchGetterLength = ref([]);   

watch(arr, () => _watch.value = [...arr.value]);
watch(arr.value, () => _watchValue.value = [...arr.value]);
watch(arr, () => _watchDeep.value = [...arr.value], { deep: true });
// watch(arr.value.length, () => _watchLength.value = [...arr.value]);
watch(() => arr.value, () => _watchGetter.value = [...arr.value]);
watch(() => arr.value.length, () => _watchGetterLength.value = [...arr.value]);
</script>

<template>
  <table>
    <tbody>
      <tr><td>模板</td><td>{{ arr }}</td></tr>
      <tr><td>watch(arr)</td><td>{{ _watch }}</td></tr>
      <tr><td>watch(arr.value)</td><td>{{ _watchValue }}</td></tr>
      <tr><td>watch(arr, { deep: true })</td><td>{{ _watchDeep }}</td></tr>
      <tr><td>arr.value.length</td><td>{{ _watchLength }}</td></tr>
      <tr><td>watch(() => arr.value)</td><td>{{ _watchGetter }}</td></tr>
      <tr><td>watch(() => arr.value.length)</td><td>{{ _watchGetterLength }}</td></tr>
    </tbody>
  </table>

  <div class="button-group">
    <button @click="arr.push(counter++)">arr.push(i)</button>
    <button @click="arr.splice(0, 1)">arr.splice(0, 1)</button>
    <button @click="arr[0] = counter++">arr[0] = i</button>
    <button @click="arr = [counter]">arr = [i]</button>
    <button @click="arr.length = 0">arr.length = 0</button>
  </div>
</template>

<style>
/* 样式由 Claude Sonnet 3.5 完成 */
* {
  font-family: consolas;
}
table {
  border-collapse: collapse;
  margin: 20px 0;
  width: 100%;
  max-width: 800px;
}

td {
  border: 1px solid #ddd;
  padding: 8px 12px;
}

td:first-child {
  font-weight: bold;
  background-color: #f5f5f5;
  width: 300px;
}

.button-group {
  margin: 20px 0;
}

button {
  margin-right: 10px;
  padding: 8px 16px;
  background-color: #4CAF50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover {
  background-color: #45a049;
}

button:last-child {
  margin-right: 0;
}
</style>
Vue 3 中的 `ref` 是用于响应式数据管理的一种工具,当你期望某个值可以响应改变并在视图中自动更新时,通常会使用 `ref`。然而,如果你创建了一个 `ref` 数组,并尝试监听它的变化,但没有看到预期的效果,可能是以下几个原因: 1. **初始化和赋值**:确保你在创建 `ref` 时已经正确地初始化了它。如果直接设置一个空数组,`watch` 或者 `v-on` 事件不会监听到改变。 ```javascript const items = ref([]); // 后续修改items items.value.push(&#39;item&#39;); // 而不是 items[] = [&#39;item&#39;] ``` 2. **监听方式**:你可以通过 `.sync` 版本的 `watch` 或者 `v-model` 来实时监听数组变化,而不是直接用普通的 `watch`。 ```javascript watch(items, (newItems) => { console.log(newItems); }, { deep: true }); ``` 3. **深拷贝问题**:如果数组内容是引用类型(如对象),记得使用 `deep` 模式或者在赋值前做深度克隆,因为 Vue 只有浅拷贝才会触发更新。 4. **`Array.observe` 已移除**:从 Vue 3 开始,不再直接支持 `Array.observe` 功能,你需要手动处理添加、删除和替换操作。 5. **异步更新**:如果你在修改 `ref` 数组后立即触发监听,可能会因为 Vue 的内部调度机制而未立即生效,可以在适当的地方加一个 `nextTick`。 检查以上点并修复,应该能解决 `ref` 数组监听不到变更的问题。如果你还有疑问,提供具体的代码片段将有助于更好地分析。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值