Vue3:从浅/深拷贝深入浅出reactive响应式缺失的原因
reactive的准则
在了解reactive响应式缺失的原因之前,我们要先了解关于reactive的两条准则。
1.reactive 只能代理对象(Object、Array、Map、Set),不能包装原始值(string、number、boolean、symbol)。
2.reactive 返回的是 Proxy 实例,你必须全程持有同一个引用;替换整个对象或解构/展开都会丢失代理。
缺失原因
1.解构后使用 → 脱离代理
这可以被理解为一种“值的拷贝”,它甚至比浅拷贝还要“浅”。
<script setup>
import { reactive } from 'vue'
const title = '2. 解构丢失'
const state = reactive({ a: 1 })
let { a } = state // ❌ 普通值
function run() {
a++ // 只改了普通变量
}
</script>
-
state.a被访问(触发 Proxy 的get)。 -
它返回了原始值,也就是1。
-
const a = 1
此时,变量 a只是一个本地的、普通的变量。它与 state 这个 Proxy 对象之间没有任何联系。
正确修复:解构使用toRefs
当要修改a值时:
a=2
并没有去修改 state 对象,因此 Proxy 的 set 拦截器根本不会被触发。Vue 完全不知道发生了变化。
用拷贝来类比: 这就像你把 state.a的值(1)拷贝到了一张新纸(变量 a)上。你在这张新纸上写写画画,并不会影响到 state 那个“魔法笔记本”(Proxy)。
正确修改方式:
import { toRefs } from 'vue'
const { a } = toRefs(state) // ✅ 保持响应式引用
function run() {
a.value++
}
2.浅拷贝导致响应缺失
浅拷贝(如 { ...obj }、Object.assign、lodash.clone)的核心是「只复制表层属性值」,深层对象仍指向原始 reactive 代理的深层属性(因为 reactive 是深层响应式,原始对象的深层属性也是 Proxy 实例)。
import { reactive, watchEffect } from 'vue';
// 原始 reactive 代理对象(表层+深层均为 Proxy)
const original = reactive({
name: '张三', // 表层属性(值类型)
info: { age: 18 } // 深层对象(引用类型,也是 Proxy)
});
// 浅拷贝:生成普通对象 copy(非 Proxy)
const copy = { ...original };
// 监听原始对象的变化
watchEffect(() => {
console.log('original.name:', original.name);
console.log('original.info.age:', original.info.age);
});
// 1. 修改拷贝对象的「表层属性」
copy.name = '李四';
// 👉 无响应!因为 copy 是普通对象,修改不经过 Proxy 拦截,无法触发依赖更新
// 2. 修改拷贝对象的「深层属性」
copy.info.age = 19;
// 👉 有响应!因为 copy.info 仍是原始 reactive 的深层 Proxy 实例,修改经过拦截
- 响应式丢失的是「拷贝对象的表层属性」:因为拷贝生成的是普通对象,表层属性的修改不经过原始
Proxy拦截。 - 深层属性仍有响应式:仅因为深层对象未被复制,仍复用原始
Proxy实例(并非拷贝对象本身有响应式)。
3.深拷贝导致响应缺失
深拷贝(如 JSON.parse(JSON.stringify)、lodash.cloneDeep)的核心是「递归复制所有层级属性」,生成完全独立的普通对象 ——无论表层还是深层,都与原始 Proxy 实例无任何引用关系。
import { reactive, watchEffect } from 'vue';
import _ from 'lodash';
const original = reactive({
name: '张三',
info: { age: 18 }
});
// 深拷贝:生成完全独立的普通对象 deepCopy(所有层级均非 Proxy)
const deepCopy = _.cloneDeep(original);
// 或 JSON.parse(JSON.stringify(original))
watchEffect(() => {
console.log('original.name:', original.name);
console.log('original.info.age:', original.info.age);
});
// 1. 修改表层属性
deepCopy.name = '李四';
// 👉 无响应!deepCopy 是普通对象,修改不经过 Proxy
// 2. 修改深层属性
deepCopy.info.age = 19;
// 👉 无响应!deepCopy.info 是普通对象(深拷贝已复制为新对象),与原始 Proxy 无关
所有层级的响应式均丢失:深拷贝彻底切断了与原始 Proxy 的引用,拷贝对象的所有属性都是普通值 / 普通对象,修改时无法触发 Proxy 的 get/set 拦截,自然无响应式。
防止响应式缺失方法:
- 对拷贝结果重新用
reactive包裹:const copy = reactive({ ...original })(重新创建Proxy实例)。 - 用
toRefs解构属性:const { name, info } = toRefs(original)(toRefs会保留属性的响应式关联)。 - 避免直接拷贝
reactive对象,优先操作原始Proxy实例。
总结:
reactive 的响应式依赖「Proxy 实例的拦截机制」,而拷贝(浅 / 深)的核心是「生成新的普通对象」:
- 浅拷贝:表层属性脱离
Proxy,响应式丢失;深层属性仍关联原始Proxy,暂留响应式(但非拷贝对象自身的响应式)。 - 深拷贝:所有层级均脱离
Proxy,完全丢失响应式。
更多推荐


所有评论(0)