您列出的这七个方法非常准确,它们是 Vue 中被称为**“变更方法” (mutation methods)** 的核心。当您对一个响应式数组调用这些方法时,Vue 能够侦测到这些操作并自动更新视图。
我们来深入探讨一下这背后的原理,以及与此相关的注意事项。
在 Vue 3 中,当你用 ref([])
或 reactive([])
创建一个响应式数组时,你得到的其实不是一个普通的 JavaScript 数组,而是一个该数组的代理 (Proxy) 对象。
你可以把这个 Proxy
想象成一个非常智能的“管家”,它包裹着你真实的数组。
myArray.push('新成员')
时,你并不是直接在操作原始数组,而是先在请求“管家”去做这件事。push()
操作。Vue 随后会安排一次视图更新,最终将变化渲染到页面上。这七个方法(push
, pop
, shift
, unshift
, splice
, sort
, reverse
)都被这个“管家”特别关照,确保一经调用,就能触发更新。
与 Vue 2 的对比:这是一个巨大的进步。在 Vue 2 中,由于其依赖
Object.defineProperty
的限制,Vue 必须通过“重写”这七个方法来实现侦测。而在 Vue 3 中,Proxy
的能力使得数组操作的侦测更全面、更底层。
下面的例子直观地展示了这些方法是如何触发视图更新的。
DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>数组变化侦测title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js">script>
<style>
body { font-family: sans-serif; padding: 20px; }
#app { display: flex; gap: 40px; }
.controls button { display: block; margin-bottom: 10px; width: 120px; }
.list li { background: #f0f0f0; margin-bottom: 5px; padding: 5px; }
style>
head>
<body>
<div id="app">
<div class="controls">
<h4>变更方法h4>
<button @click="items.push({ id: Date.now(), text: 'New Item' })">push()button>
<button @click="items.pop()">pop()button>
<button @click="items.shift()">shift()button>
<button @click="items.unshift({ id: Date.now(), text: 'First Item' })">unshift()button>
<button @click="items.splice(1, 1)">splice(1, 1)button>
<button @click="items.sort((a, b) => a.text.localeCompare(b.text))">sort()button>
<button @click="items.reverse()">reverse()button>
div>
<div class="display">
<h3>当前数组内容:h3>
<ul class="list">
<li v-for="item in items" :key="item.id">
ID: {{ item.id }}, Text: {{ item.text }}
li>
ul>
<p v-if="items.length === 0">数组为空p>
div>
div>
<script>
const { createApp, ref } = Vue;
createApp({
setup() {
const items = ref([
{ id: 1, text: 'Apple' },
{ id: 2, text: 'Banana' },
{ id: 3, text: 'Cherry' },
]);
return { items };
}
}).mount('#app');
script>
body>
html>
有些数组方法不会修改原始数组,而是返回一个全新的数组,例如 filter()
, concat()
, slice()
, map()
。
当你使用这些方法时,Vue 无法侦测到变化,因为原始的响应式数组没有被“变更”。
错误的做法 ❌
// 这么做是无效的!
// .filter() 返回了一个新数组,但 items.value 本身没变
items.value.filter(item => item.id !== 1);
正确的做法 ✅
你需要用这些方法返回的新数组,去替换掉旧的数组。
// 用 .filter() 返回的新数组,重新赋值给 items.value
items.value = items.value.filter(item => item.id !== 1);
这种“替换”操作,Vue 是能够侦测到的,从而触发视图更新。
这是一个非常重要的点,体现了 Vue 3 相对于 Vue 2 的优势。
myArray[0] = 'newValue'
不会触发更新。Proxy
的强大能力,直接通过索引修改数组是完全响应式的!// 在 Vue 3 中,这会正常触发视图更新!
items.value[0].text = 'Apricot';
同样,直接修改数组的 length
属性在 Vue 3 中也是响应式的。
操作方式 | 示例 | Vue 3 是否能侦测? | 备注 |
---|---|---|---|
变更方法 | items.push(...) |
✅ 是 | 直接修改原数组并触发更新,是首选方式。 |
非变更方法 | items.filter(...) |
❌ 否 (操作本身) | 必须将返回的新数组重新赋值给原始变量:items.value = ... |
通过索引直接修改 | items.value[0] = ... |
✅ 是 | Vue 3 的巨大改进,可以直接使用。 |
理解这些细微的差别,可以帮助你更有效地在 Vue 中管理和操作数组数据。