Vue3数据失去响应式的原因以及解决方法

一、使用 reactive 定义的数据重新赋值

vue 复制代码
<template>
  <h1>{{ foo.a }}</h1>
  <h1>{{ bar.a }}</h1>
  <button @click="handleClick">点我</button>
</template>
<script setup>
import {  ref, reactive } from 'vue'

let foo = ref({ a: 1, b: 2, c: 3 })
let bar= reactive({ a: 1, b: 2, c: 3 })

const handleClick = () => {
  foo.value = {
    a: 11
  }
  bar= {
    a: 99
  } 
}
</script>

原因: 为什么 ref 定义的对象重新赋值后不会丢失响应式,而 reactive 会丢失响应式

详解:

vue 复制代码
import {ref,reactive} from 'vue';
let test = {age:2};
let obj = reactive({age:1})
let obj1 = ref({age:1})

obj = test;  //在vue2的响应式中,人们习惯直接赋值了。在进入到vue3的时候,大部分的开发者没有看文档或者基于vue2的习惯,会进行这样的赋值情况。比如对象的再次初始化的情况。
obj1.value = test;

通过reactive()包含的对象是进行了内部的proxy代理,因此具有响应式。但是像test这个对象,它是没有进行数据劫持的,而对象赋值的时候实际上是引用地址赋值。那么obj这个对象变成了一个没有数据劫持的引用地址,那么它也就失去了响应式。 但是obj1重新赋值时会保留自身的响应式。其实很简单,跟上图的代码是有关的。细心的人会发现,在 set 函数里面有这么一段代码。

是的,在我们对ref定义的变量重新赋值时会进入 set 函数,且重新赋值的是一个对象的话,那么它会再次进入 toReactive 函数进行数据劫持,这就是为什么ref定义的变量重新赋值对象时依旧保留响应式的根本原因。

vue 复制代码
let refObj = ref({name:1});
//内部set函数触发,进行判断,发现赋值是一个对象,那么就会对对象进行reactive,即等同于
refObj = reactive({value:{name:1}});
refObj.value.name = 1;  //以上就是ref不丢失响应式的本质

二、响应式数据被解构赋值(大多是 props 中的数据被解构赋值)

vue 复制代码
<template>
  <h1>{{ a.b.c }}</h1>
  <h1 v-if="a.b.d">{{ a.b.d }}</h1>
  <h1>{{ aa.bb }}</h1>
  <h1>{{ aaa }}</h1>
</template>
<script setup>
import { ref, onMounted} from 'vue'
const obj = {
  a: {
    b: {
      c: 1
    }
  },
  aa: {
    bb: 11
  },
  aaa: 111
}
let testObj = ref(obj)
let { a, aa, aaa } = testObj.value

onMounted(() => {
  setTimeout(() => {
    a.b.c = 9
    a.b.d = 9
    aa.bb = 99
    aaa = 999
  }, 3000)
})
</script>

上列数据只有 aaa 丢失了响应式

原因:

我们知道解构赋值,区分原始类型的赋值,和引用类型的赋值,原始类型的赋值相当于按值传递,引用类型的值就相当于按引用传递

按值传递

js 复制代码
 // 假设a是个响应式对象
	a.b = 1
 // c 此时就是一个值跟当前的 a 已经不沾边了
	const c=a.b // 相当于 c = 1

你直接访问 c 就相当于直接访问这个值,也就绕过了 a 对象的 getter。所以最开始的例子中,aaa 失去了响应式

按引用传递

js 复制代码
 // 假设 a 是个响应式对象
 const a.b.c = 3
 // 当你访问a.b的时候就已经重新初始化响应式了,此时的 c 就已经是一个代理的对象
 const c=a.b

你直接访问 c 就相当于访问一个响应式对象,所以并不会失去响应式

为什么 a.b 是响应式的呢?

源码中 reactive 方法调用了 createReactiveObject 方法,createReactiveObject 方法中,有一个判断,来检查数据是否是响应式的

js 复制代码
// target already has corresponding Proxy
    const existingProxy = proxyMap.get(target);
    if (existingProxy) {
        return existingProxy;
    }

注释表明了这个 if 的作用是,判断目标对象是否是响应式,如果是则返回,所以 c 得到的还是响应式的数据

结论:对响应式数据进行解构赋值,如果解构出来的数据是 基本数据类型,则会丢失响应式,如果是 引用类型,则不会丢失响应式。

如果解构出来的数据是 基本数据类型,可以用Vue3提供的toRefs函数来将逝去响应式的对象来转换为响应式的对象

vue 复制代码
const state = reactive({
	count: 0,
	message: 'jack'
})
const { count, message } = toRefs(state)

三、使用vuex的数据进行赋值

vue 复制代码
<template>
  <h1>{{ test }}</h1>
  <h1>{{ testCom }}</h1>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
// store.state.testData的初始值为 '11111'
let test = ref(store.state.testData) // 相当于let test = ref('11111')
// 解决办法
let testCom = computed(() => {
  return store.state.testData
}) 

onMounted(() => {
  setTimeout(() => {
    store.commit('getTestData', '12345')
  }, 3000)
})
</script>

原因: 还是变量赋值的原因,就不赘述了

解决办法: 用computed 包裹起来,其实道理是一样的,也是变量赋值的原因,

相关推荐
加班是不可能的,除非双倍日工资4 小时前
css预编译器实现星空背景图
前端·css·vue3
wyiyiyi5 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip5 小时前
vite和webpack打包结构控制
前端·javascript
excel5 小时前
在二维 Canvas 中模拟三角形绕 X、Y 轴旋转
前端
阿华的代码王国6 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼6 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin
Jimmy6 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
草梅友仁6 小时前
草梅 Auth 1.4.0 发布与 ESLint v9 更新 | 2025 年第 33 周草梅周报
vue.js·github·nuxt.js
ZXT6 小时前
promise & async await总结
前端
Jerry说前后端6 小时前
RecyclerView 性能优化:从原理到实践的深度优化方案
android·前端·性能优化