咪咕体育直播app
159.91MB · 2025-11-12
Vue3的响应式系统基于Proxy实现,通过代理对象(或数组)跟踪属性的读取(get)和修改(set)操作。当属性变化时,Vue会自动触发依赖更新,同步视图。
但Proxy并非“全能”——它只能跟踪初始存在的属性和已知的变更操作,对于一些“非常规”修改(如新增属性、直接修改数组索引),无法自动触发响应,需要我们手动规避。
用reactive创建的响应式对象,新增属性或删除属性时,无法触发视图更新。因为Proxy默认只跟踪对象初始化时的已有属性,新增/删除的属性不在初始跟踪范围内。
示例(错误用法):
import { reactive } from 'vue';
const user = reactive({ name: 'Alice' });
// 新增属性:视图不更新
user.age = 20;
// 删除属性:视图不更新
delete user.name;
针对对象的新增/删除操作,Vue3提供了3种标准解决方案:
set/delete函数set(Vue.set的简写)用于向响应式对象新增属性,delete(Vue.delete的简写)用于删除属性,两者都会触发响应式更新。
示例(正确用法):
import { reactive, set, delete as vueDelete } from 'vue';
const user = reactive({ name: 'Alice' });
// 新增响应式属性
set(user, 'age', 20); // 视图更新为 { name: 'Alice', age: 20 }
// 删除响应式属性
vueDelete(user, 'name'); // 视图更新为 { age: 20 }
通过扩展运算符(...)创建新对象,替换原对象。新对象会继承原对象的响应式能力,触发视图更新。
示例:
const userRef = ref({ name: 'Alice' }); // 用ref包裹对象
// 新增属性:生成新对象
userRef.value = { ...userRef.value, age: 20 }; // 响应式更新
如果提前知道对象的所有属性,可以在reactive初始化时就定义(值为undefined),后续修改时会自动触发响应。
示例:
const user = reactive({ name: 'Alice', age: undefined }); // 初始定义age
user.age = 20; // 响应式更新(视图显示20)
用reactive或ref包裹的数组,直接修改索引或修改长度时,Vue3早期版本无法触发响应(官网文档仍保留此说明)。尽管最新版本(v3.4+)已支持,但为了兼容性,仍建议用变异方法或set函数。
示例(不推荐用法):
const list = ref([1, 2, 3]);
// 直接修改索引:早期版本不响应
list.value[0] = 4;
// 修改长度:早期版本不响应
list.value.length = 2;
Vue3推荐用数组变异方法或set函数修改数组,确保响应式:
Vue3自动跟踪数组的变异方法(修改原数组的方法),这些方法会触发响应式更新。常用变异方法:
push():末尾添加元素pop():末尾删除元素splice():插入/删除/替换元素sort():排序reverse():反转示例:
const list = ref([1, 2, 3]);
// 修改索引0的值:用splice
list.value.splice(0, 1, 4); // 替换第0个元素为4(响应式)
// 修改长度:用splice
list.value.splice(2); // 删除索引2及之后的元素(长度变为2,响应式)
set函数set函数可直接修改数组的指定索引,触发响应式更新。
示例:
import { ref, set } from 'vue';
const list = ref([1, 2, 3]);
// 修改索引0的值:响应式
set(list.value, 0, 4);
reactive无法代理reactive只能代理对象或数组,无法代理原始值(如number、string、boolean)。直接用reactive包裹原始值会报错。
示例(错误用法):
import { reactive } from 'vue';
// 报错:value cannot be made reactive: 0
const count = reactive(0);
refref是Vue3专门用于代理原始值的API,它会将原始值包裹在一个带value属性的响应式对象中。访问或修改时需通过.value(模板中无需.value,Vue会自动解包)。
示例(正确用法):
import { ref } from 'vue';
// 用ref代理原始值
const count = ref(0);
// 修改值:响应式
count.value++; // 变为1
// 模板中使用:自动解包
// <div>{{ count }}</div> // 显示1
我们用上述知识实现一个购物车功能,包含商品的添加、删除和数量修改:
<template>
<div class="cart">
<h2>购物车</h2>
<!-- 商品列表 -->
<div v-for="(item, index) in cart" :key="item.id" class="cart-item">
<span>{{ item.name }}</span>
<button @click="decrement(index)">-</button>
<span>{{ item.quantity }}</span>
<button @click="increment(index)">+</button>
<button @click="removeItem(index)">删除</button>
</div>
<!-- 总价 -->
<p class="total">总价:{{ totalPrice }} 元</p>
<!-- 添加商品按钮 -->
<button @click="addItem" class="add-btn">添加商品</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
// 1. 用ref包裹购物车数组(支持响应式)
const cart = ref([
{ id: 1, name: '商品A', quantity: 1, price: 100 },
{ id: 2, name: '商品B', quantity: 2, price: 200 }
]);
// 2. 计算总价(响应式:依赖cart的变化)
const totalPrice = computed(() => {
return cart.value.reduce((sum, item) => sum + item.quantity * item.price, 0);
});
// 3. 增加数量:用splice修改数组(遵循官网推荐)
function increment(index) {
const item = cart.value[index];
// 生成新商品对象(避免直接修改原对象)
const newItem = { ...item, quantity: item.quantity + 1 };
// 用splice替换原商品(触发响应式)
cart.value.splice(index, 1, newItem);
}
// 4. 减少数量:用splice修改数组
function decrement(index) {
const item = cart.value[index];
if (item.quantity > 1) {
const newItem = { ...item, quantity: item.quantity - 1 };
cart.value.splice(index, 1, newItem);
}
}
// 5. 删除商品:用splice修改数组
function removeItem(index) {
cart.value.splice(index, 1);
}
// 6. 添加商品:用push修改数组
function addItem() {
const newItem = {
id: Date.now(), // 用时间戳作为唯一ID
name: `商品${cart.value.length + 1}`,
quantity: 1,
price: Math.floor(Math.random() * 100) + 50 // 随机单价(50-149元)
};
cart.value.push(newItem); // push是变异方法,触发响应式
}
</script>
<style scoped>
.cart { max-width: 400px; margin: 20px auto; padding: 20px; border: 1px solid #eee; }
.cart-item { display: flex; align-items: center; margin: 10px 0; }
.cart-item span { margin: 0 10px; }
.total { font-weight: bold; margin: 20px 0; }
.add-btn { padding: 8px 16px; background: #42b983; color: #fff; border: none; border-radius: 4px; }
</style>
ref包裹数组:cart是ref对象,cart.value是响应式数组,修改时需通过.value。splice(修改数量、删除商品)、push(添加商品)都是数组变异方法,确保响应式。computed:totalPrice依赖cart的变化,自动更新总价,无需手动触发。用reactive创建的对象const user = reactive({ name: 'Alice' }),如何新增响应式属性age?(至少2种方法)
用ref包裹的数组const list = ref([1,2,3]),如何修改索引0的值为4?(至少2种方法)
问题1答案:
set函数:set(user, 'age', 20)。const userRef = ref(user); userRef.value = { ...userRef.value, age: 20 }(reactive对象不能直接赋值,需用ref包裹)。问题2答案:
splice:list.value.splice(0, 1, 4)。set函数:set(list.value, 0, 4)。list.value[0] = 4。value cannot be made reactive: 0reactive无法代理原始值(如number)。ref代替reactive:const count = ref(0)。Cannot add property age, object is not extensiblereactive对象被冻结(Object.freeze),无法新增属性。ref包裹后修改.value。TypeError: Cannot read properties of undefined (reading 'value')ref对象未初始化,或模板中错误使用.value。ref初始值非undefined,模板中直接用变量名(如{{ count }}而非{{ count.value }})。ref:reactive只代理对象/数组,原始值优先用ref。push、splice等变异方法,避免直接修改索引。set:新增对象属性时,用set函数而非直接赋值。