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 包裹起来,其实道理是一样的,也是变量赋值的原因,

相关推荐
子兮曰7 分钟前
Agency-Agents 深度解析:400+ AI 专家的"梦之队"如何重塑开发工作流
前端·后端·vibecoding
竹林8181 小时前
用 The Graph 查询链上数据实战:从手搓 RPC 到 Subgraph,我的 NFT 项目数据加载快了 10 倍
前端·javascript
妙码生花1 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十九):点选验证码代码逐行目检
前端·后端·go
Awu12272 小时前
⚡从零开发 Agent CLI(五)实现一个可治理、可扩展的工具系统
前端·人工智能·claude
咪库咪库咪2 小时前
Vue3-生命周期
前端
莪_幻尘3 小时前
你的 AI Skill 越多越蠢?Token 上下文爆炸的求生指南
前端·ai编程
lichenyang4533 小时前
从 has.echo 到异步 API 注册表:一次 ASCF API 回调不触发的排查复盘
前端
林瞅瞅3 小时前
Nuxt3 项目部署 Nginx 防盗链后特定 JS 文件 403 问题修复方案
前端
kyriewen4 小时前
别再每次都 Google 了:我整理了前端日常最常踩的 10 个 Git 坑,附速查表
前端·javascript·git
一颗奇趣蛋4 小时前
Web 视频开发完全指南:从入门到精通
前端