Vue3中非深层嵌套的响应式变量为何无法触发重新渲染?
我正在学习Vue3的Composition API响应式原理,想要实现一个能即时在模板中展示结果的搜索栏。我声明了响应式变量search和results:当用ref([])(示例A)或直接用reactive([])(示例B)声明results时,模板无法即时渲染结果——输入第一个字符无反应,输入第二个字符时才显示上一次的搜索结果;但将results嵌套在reactive对象中(示例C)时,就能即时显示所有结果。请问为何示例A和B无法正常工作?
代码示例
<script setup> let search = ref(""); // EXAMPLE A // 无法正常工作,搜索结果会滞后一个输入周期显示 const results = ref([]); // EXAMPLE B // 无法正常工作,搜索结果会滞后一个输入周期显示 const results = reactive([]); // EXAMPLE C // 正常工作,将结果数组嵌套在reactive对象中 const query = reactive({ results: [] }); watch(search, (value) => { axios .post("/search", { search: value }) .then(response => { // EXAMPLE A / B 的写法 results = response.data; // EXAMPLE C 的写法 query.results = response.data; }) .catch(); }); </script> <template> <!-- EXAMPLE A / B 对应的模板 --> <div v-for="product in results" :key="product.id"> <div>{{ product.id }}</div> <div>{{ product.name }}</div> </div> <!-- EXAMPLE C 对应的模板 --> <div v-for="product in query.results" :key="product.id"> <div>{{ product.id }}</div> <div>{{ product.name }}</div> </div> </template>
原因分析
示例A的问题
用ref([])创建的results是一个响应式包装对象,它的实际数组值存储在.value属性中。你在回调里直接执行results = response.data,相当于把原本的响应式对象替换成了普通数组,模板绑定的还是最初的空响应式数组,直到下一次search变化触发watch时,模板才会“滞后”显示上一次的结果。
正确写法应该是修改.value:
results.value = response.data;
示例B的问题
reactive([])创建的响应式数组是基于Proxy的代理对象,Vue无法追踪“直接替换整个数组”的操作。当你执行results = response.data时,原响应式代理被普通数组覆盖,模板和新数组之间的响应式绑定直接断开,自然无法即时更新。
正确做法是修改数组本身的内容,比如用splice清空并替换:
results.splice(0, results.length, ...response.data);
示例C为什么正常
query是reactive创建的响应式对象,你修改的是它的results属性——Vue的Proxy能捕获到对象属性的赋值操作,因此会立即触发模板更新。这里没有替换query本身,只是修改了它的内部属性,响应式链路始终保持完整。
总结
- 用
ref创建的响应式变量,必须通过.value修改值,不能直接赋值覆盖原响应式对象。 - 用
reactive创建的响应式数组/对象,不能直接替换整个实例,要修改内部元素或属性;如果需要频繁替换数据,优先用ref更省心。
内容的提问来源于stack exchange,提问作者MaVaBo

