reactive创建的响应式对象解构后为什么会失去响应式

本文通过实例解析Vue3中reactive方法的工作原理,并探讨了解构响应式对象导致失去响应式特性的原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

自从专注于React之后,已经有段时间没有用过Vue了,因此最近在学习Vue3.0,Vue的更新机制与React的更新机制是不同的,React的更新机制说的简单点就是根据场景调用不同方法触发状态更新,从而再次进行渲染,而对于Vue的话,我认为是在发生的数据更新时,根据情况进行渲染的。说的专业点就是Vue拥有一个响应式系统,可以让它在数据更新的时候自动进行视图的更新。在Vue3.0中,可以使用reactive声明响应式状态 ,官方例子:

import { reactive } from 'vue'

// 响应式状态
const state = reactive({
  count: 0
})

也可以创建独立的响应式值:

import { reactive } from 'vue'

// 响应式状态
import { ref } from 'vue'

const count = ref(0)

那么本文重点来了,在学习到reactive方法的时候,看到文档说不要解构reactive创建的响应式对象,为什么?,因为会失去响应式的功能,那为什么会失去,因为…(并没有解释),而且在观看一个尤大讲解Vue3的视频中,尤大也说了不要解构reactive创建的响应式对象,为什么?,因为会失去响应式的功能,那为什么会失去,因为…(并没有解释)。这个问题隔了几天直到我调试Vue3.0的reactivity之后,我才有了一丝想法,可以简单的看看在调用reactivity方法之后发生了什么:
①先有个例子:

 <div id="app">
        <p @click="change">
            {{refCount}}
            {{count}}
        </p>
    </div>
		//js代码
        const { reactive, toRefs } = Vue;
        const vm = Vue.createApp({
            setup(props) {
            //创建响应式对象
                const rObj = reactive({
                    count: 0
                });
            //创建点击事件
                const change = () => {
                    console.log(rObj.count)
                    rObj.count++;
                }
            //利用ref保留与源对象的响应式关联
                const { count:refCount } = toRefs(rObj);
                const { count } = rObj;
                return {
                	//具有响应式
                    refCount,
                 	//响应式丢失
                    count,
                    change
                }
            }
        }).mount('#app');

可以看出当点击元素的时候,refCount是随着进行变化的,而count是解构出来的,而它并没有随着变化的。那么调试进入reactive方法:
在这里插入图片描述
可以看到它在内部调用了createReactiveObject方法,这名字很好理解就是创建响应式对象,调试进去:
在这里插入图片描述
它在这里做了一些处理,细节暂时不探究,重点在这:
在这里插入图片描述
创建了一个代理对象将其保存在内部并返回,学习Vue3或多或少都听过Vue3如何进行数据视图更新的,Proxy对数据进行了拦截,当数据发生变化,通知Vue进行视图变化,在new Proxy的第二个参数,它通常使用的baseHanderls:

  const baseHandlers = {
        get(target, key, receiver) {},
        set(target, key, value, receiver) {},
        deleteProxy: (target, key) {},
        has: (target, key) {},
        ownKey: (target) {}
    };

绕了这么久可以看出reactive方法其实就是创建了一个Proxy对象,以及进行了一系列处理,其中并没有找到它可能会失去响应式的情况,也就是说它失去响应式不在于Vue而是在于Proxy对象本身,那么可以简化一下:

		const obj = {
            	count: 1
        	};
        const proxy = new Proxy(obj, {
            get(target, key, receiver) {
                console.log("这里是get");
                return Reflect.get(target, key, receiver);
            },
            set(target, key, value, receiver) {
                console.log("这里是set");
                return Reflect.set(target, key, value, receiver);
            }
        });

打印出来的效果:
在这里插入图片描述
这时想一下如何会因为解构而失去响应式(在这里就当作是打印文字),先试试直接赋值会打印啥:

 proxy.count = 2;

在这里插入图片描述

是因为解构相当于重新赋值给另一个变量的原因吗,也就是说它变成了一个新值:

		let {count} = proxy;
        count = 2;

这里会打印什么,因为失去响应,所以它只打印出来:
在这里插入图片描述
好像是这样的感觉,那试试对象:

		 const obj = {
            a: {
                count: 1
            }
        };
        function reactive(obj) {
            return new Proxy(obj, {
                get(target, key, receiver) {
                    console.log("这里是get");
                    if (typeof target[key] === "object") {
                        return reactive(target[key]);
                    };
                    return Reflect.get(target, key, receiver);
                },
                set(target, key, value, receiver) {
                    console.log("这里是set");
                    return Reflect.set(target, key, value, receiver);
                }
            });
        };
        const proxy = reactive(obj);

直接赋值:

   		proxy.a.count = 2;
        console.log(proxy);

在这里插入图片描述

先只解构一次看看

		let { a } = proxy;
        a.count = 3;
        console.log(proxy);

在这里插入图片描述
解构到底

		let { a } = proxy;
        let { count } = a;
        count = 3;
        console.log(proxy);

在这里插入图片描述
为什么会不同,其实很好理解,只解构一次,其实新的对象的count仍然是被代理的,
在这里插入图片描述

而解构两次,直接获取了count,相当于绕过了代理a。
那么就很好理解了,解构为什么会失去响应,用这个图就可以解释:
在这里插入图片描述
因为解构赋值相当于直接跳过了代理那一层,在下面直接获取值,所以get和set无法被调用。

<think>我们正在讨论Vue3中解构赋值导致失去响应式的问题。根据引用[1][2][3],我们知道在Vue3中,如果直接对响应式对象进行解构赋值,会失去响应性。这是因为Vue3的响应式系统基于Proxy,而解构得到的是原始值,不再是代理对象。解决方案:1.使用`toRefs`函数:将响应式对象转换为普通对象,其中每个属性都是指向源对象相应属性的ref引用。这样在解构后仍能保持响应性。2.使用`toRef`函数:针对单个属性,创建一个ref对象,保持对源属性的响应式连接。具体操作:情况一:解构整个响应式对象使用`toRefs`将响应式对象的每个属性都转换为ref,然后解构。情况二:只需要解构部分属性可以使用`toRef`单独为某个属性创建ref。情况三:在组合式函数中返回响应式对象在组合式函数中,使用`toRefs`处理返回的对象,以便在外部解构时保持响应性。示例代码:1.使用`toRefs`解构整个对象```javascriptimport{reactive,toRefs}from'vue';conststate=reactive({name:'张三',age:20});//使用toRefs转换后再解构const{name,age}=toRefs(state);//现在name和age都是ref对象,需要.value访问console.log(name.value);//张三name.value='李四';//修改会更新原始state```2.使用`toRef`解构单个属性```javascriptimport{reactive,toRef}from'vue';conststate=reactive({name:'张三',age:20});//只解构name属性并保持响应式constname=toRef(state,'name');console.log(name.value);//张三name.value='李四';//有效```3.在组合式函数中使用```javascriptimport{reactive,toRefs}from'vue';//组合式函数functionuseUser(){conststate=reactive({name:'张三',age:20});//一些操作...//返回时转换为refsreturntoRefs(state);}//在使用组合式函数时解构const{name,age}=useUser();//解构后仍然保持响应式```注意事项:-`toRefs`只会转换源对象上可枚举的属性(不包括原型链上的属性)。-如果要转换的属性可能不存在,应使用`toRef`,因为它会为不存在的属性创建一个可写的ref(而`toRefs`不会转换不存在的属性)。-解构后的属性是ref对象,访问和修改时需要使用`.value`。为什么这样能保持响应式?因为`toRefs`和`toRef`创建的ref对象内部保持了与源响应式对象属性的连接。当源对象属性变化时,ref对象会得到更新;反之,修改ref对象也会更新源对象。引用[3]中给出了一个在`setup`中使用`toRefs`的例子,以及在`<scriptsetup>`中在组合式函数返回时使用`toRefs`以确保解构失去响应性。因此,在Vue3中要解构响应式对象而不失去响应性,应使用`toRefs`或`toRef`。相关问题:1.`toRefs`和`toRef`在实现原理上有什么区别?2.除了解构赋值,Vue3中还有哪些操作会导致响应式丢失?3.如何为深层嵌套对象的属性创建ref并保持响应式?4.Vue3的模板中可以直接使用解构后的ref吗?需要注意什么?</think>### Vue3 解构赋值保持响应式的解决方案 在 Vue3 中,响应式对象直接解构失去响应性,这是因为解构得到的是原始值的副本而非响应式引用。以下是完整解决方案: #### 1. 使用 `toRefs()` 方法(推荐) ```javascript import { reactive, toRefs } from 'vue' const state = reactive({ user: { name: '张三', age: 25, address: { city: '北京' } } }) // 正确解构方式 const { user } = toRefs(state) // 保持响应式连接 user.value.name = '李四' // 源对象同步更新 console.log(state.user.name) // 输出"李四" ``` **原理**:`toRefs()` 将响应式对象的每个属性转换为 ref 对象,保持与源对象响应式连接[^3]。 #### 2. 深层嵌套对象解构 ```javascript // 多层解构保持响应式 const { name, address } = toRefs(state.user) // 访问嵌套属性 address.value.city = '上海' // 触发响应式更新 ``` #### 3. 使用 `toRef()` 处理单个属性 ```javascript import { toRef } from 'vue' // 针对单个属性转换 const userName = toRef(state.user, 'name') userName.value = '王五' // 响应式生效 ``` #### 4. 组合式函数中的解构 ```html <script setup> // 组合式函数返回toRefs处理后的对象 function useUserStore() { const state = reactive({ id: 1001, profile: { /*...*/ } }) return toRefs(state) } // 安全解构 const { id, profile } = useUserStore() </script> ``` #### 5. 解构 props 的特殊处理 ```javascript import { toRefs } from 'vue' export default { props: ['userInfo'], setup(props) { // props需要显式转换 const { userInfo } = toRefs(props) return { userName: userInfo.value.name // 保持响应性 } } } ``` ### 响应式丢失原理说明 Vue3 使用 Proxy 实现响应式: 1. 直接解构 `const { a } = reactiveObj` 得到的是**值拷贝** 2. 新变量与源对象**失去连接**,不再触发响应式更新 3. `toRefs/toRef` 创建的是**指向源属性的引用**,保持响应链路[^1][^2] ### 最佳实践总结 | 场景 | 解决方案 | 注意事项 | |------|----------|----------| | 对象解构 | `toRefs()` | 返回所有属性的ref集合 | | 单属性解构 | `toRef()` | 需指定源对象和属性名 | | props解构 | `toRefs(props)` | 必须在setup内处理 | | 组合式函数 | 返回`toRefs()`结果 | 确保函数调用方直接解构 | | 模板使用 | 直接使用ref | 无需`.value`自动解包 | > **关键点**:永远不要直接解构 reactive 对象,必须通过 `toRefs()` 或 `toRef()` 转换后再解构[^3]。这样既能享受解构的便利,又能保持完整的响应式能力。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值