vue3 分析总结响应式丢失问题原因(二)

上一篇文件理解了响应式对象应用原理了。公式:

响应式对象 = 代理 + 触发器。

但是实际使用结果和预期还是不一致。具体现象是数据修改了,但是并没有实现响应式更新界面。即出现了响应式丢失现象。

一、什么情况下对象的响应式会丢失?

一般网络上查资料,都是告诉我们常见的某些操作(如解构、替换)中容易丢失响应性。只是笼统的说一下容易丢失,具体为什么丢失,会举例哪些情况会丢失,说一堆,感觉把所有情况都告诉你了,又感觉好像还缺点什么。我总结了一下,好像就是缺了一点抽象概况的总结。

所以我先说结论:造成响应式丢失的原因就是没有正确的获取到代理对象。

替换对象时丢失

首先比较简单一点,就是替换操作。例子:

let state = reactive({ count: 0 });
state = { count: 1 }; // 新对象没有响应性

进行替换操作时,可能新的值是一个实际的对象,而不是响应式对象,导致变量引用对象发送了改变,实际结果就是state响应性丢失了。这种情况比较好理解的,注意一点就好了。

解构对象时丢失

比如容易出问题的地方是解构操作。后端程序员可能对于解构概念不是很清晰,理解其语法糖本质后,后面就好理解了。

解构的基本概念

解构是一种从数组或对象中提取值的语法。它允许你将数组或对象的属性直接赋值给变量。

普通数组解构 和 等价的js代码

const arr = [1, 2, 3];
const [a, b, c] = arr; // 解构数组
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
const arr = [1, 2, 3];
const a = arr[0]; 
const b = arr[1]; 
const c = arr[2]; 
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3

普通对象解构 和 等价的js代码

const obj = { name: "Alice", age: 25 };
const { name, age } = obj; // 解构对象
console.log(name); // "Alice"
console.log(age); // 25
const obj = { name: "Alice", age: 25 };
const name = obj.name; 
const age = obj.name; 
console.log(name); // "Alice"
console.log(age); // 25

阶段总结,解构就是一个语法糖,和普通js赋值操作没什么区别。 

reactive对象解构

这个要分为两种情况:

第一种,如果解构的属性值是原始值类型,那么返回的值就是一个真实对象,而不是代理对象,即返回的值丢失了响应性。

在 JavaScript 中,原始值(Primitive Values) 是最基本的数据类型,它们不是对象,也没有方法或属性。JavaScript 有以下 7 种原始值数据类型:

number(数字)、string(字符串)、boolean(布尔值)、undefined(未定义)、null(空值)、bigint(大整数)、symbol(符号)。

一般我们最常见的就是数字和字符串了。

第二种,如果解构的属性值是原始值类型,那么返回的值就是reactive对象,即返回的值保留了响应性。

ref对象解构

这个同样要分为两种情况:

第一种,如果ref代理的实际对象值是原始值类型,那么返回的值就是一个真实对象,而不是代理对象,即返回的值丢失了响应性。

const count = ref(0); //ref对象
const { value: myCount } = count; // 解构ref
console.log(myCount); // 普通对象,丢失了响应性。

第二种,如果ref代理的实际对象值是原始值类型,那么返回的值就是reactive对象,即返回的值保留了响应性。此时如果继续解构,则参考上面的reactive对象解构。

  const aaa = ref({a:{b:"a"}});const { value } = aaa; //ref对象解构const { a } = value;//reactive对象解构const { b } = a;//reactive对象解构console.log("aaa", aaa); //ref对象console.log("value", value);//reactive对象console.log("a", a);//reactive对象console.log("b", b);// 普通对象(无响应性)console.log("value.a", value.a);//reactive对象console.log("value.a.b", value.a.b);// 普通对象(无响应性)

总结,解构时是否会丢失响应性主要看解构的返回值是否是原始值类型,是的话就会丢失响应性,否则会保留响应性。并且解构返回值要么是原始值类型,要么是reactive对象(即ref解构后也是返回reactive对象)。

换一个说法,因为解构就是一个语法糖,和普通js赋值操作没什么区别,ref对象和reactive对象读取数据返回值 与 解构的返回值 逻辑是相同的,所以我们可以得出以下结论:

  1. ref.value 读取的结果要么是原始值类型,要么是reactive对象。
  2. ref.value 或者 reactive.attr 返回结果要么是原始值类型,要么是reactive对象。

二、常见响应式应用情景分析 

computed

computed 可以看做是一个只读ref对象,即ref.value值不能修改。其他的和ref一致。

ref与reactive函数创建的对象是否是单例模式。

结论:ref创建的对象不是单例模式,reactive创建的对象是单例模式。

测试代码:

	const testObj = {a:"1"};const testObjRef1 = ref(testObj);const testObjRef2 = ref(testObj);const testObjReactive3 = reactive(testObj);const testObjReactive4 = reactive(testObj);console.log("testObjRef1.value == testObjReactive3", testObjRef1.value == testObjReactive3);console.log("testObjRef1 == testObjRef2", testObjRef1 == testObjRef2);console.log("testObjRef1.value == testObjRef2.value", testObjRef1.value == testObjRef2.value);console.log("testObjReactive4 == testObjReactive4", testObjReactive4 == testObjReactive4);

运行结果:

 

为什么关注是否单例问题,是担心不同的响应式对象变量引用的代理对象不是同一个,那么代理对象绑定的触发器可能不一样,那响应式结果可能跟预期不一样。但实际测试发现reactive创建的对象是单例的,就是同一个对象创建响应式对象使用的是同一个代理对象。而ref创建的对象虽然不是单例,但是其值如果不是原始值类型,则ref.value 值是reactive对象,是单例的。

所以,可以简单的理解为相同对象在不同组件页面或者任何地方获取的响应性都是同一个响应性对象。

子组件的属性的响应式情况

props的响应式比较特殊。既不是ref对象,也不是reactive对象。而是单独实现的一套代理逻辑。其具体逻辑如下:

先看例子:

父组件:

    const testB = ref("1");console.log("testB" , testB)let a = {a:"a"};const aRef = ref(a);const aReactive = reactive(a);let bObj = "B";const bObjRef = ref(bObj);// const bReactive = reactive(bObj); //reactive不支持基本类型watch(testB, async (newAttr, oldAttr) => {// a = {a:newAttr};  //情况1:只有props.a会跟着变,有响应式渲染。// a.a = newAttr; //情况2:props.a、props.aRef、props.aReactive 对应的值会跟着变,但是不会没有响应式渲染。// bObj = newAttr; //情况3 只有props.bObj会跟着变,有响应式渲染。// bObjRef.value = newAttr;//情况4 只有props.bObjRef会跟着变,有响应式渲染。// aReactive.a = newAttr;//情况5 props.a、props.aRef、props.aReactive 对应的值会跟着变,有响应式渲染。})
<Component  :a="a" :aRef="aRef" :aReactive="aReactive" :b="b" :bRef="bRef"></Component>

 然后再Component子组件的script中查看接收的属性:

const props = defineProps({a:Object,aRef:Object,aReactive:Object,b:String,bRef:String,});console.log("props.a", props.a);console.log("props.aRef", props.aRef);console.log("props.aReactive", props.aReactive);console.log("props.b", props.b);console.log("props.bRef", props.bRef);
<template><div> props.a ->  {{props.a}} </div><div> props.aRef ->  {{props.aRef}} </div><div> props.aReactive ->  {{props.aReactive}} </div><div> props.bObj ->  {{props.bObj}} </div><div> props.bObjRef ->  {{props.bObjRef}} </div>
</template>

运行结果:

初步结论:

  1. props既不是ref对象,也不是reactive对象,是vue3中特殊的响应式对象。他的属性值是只读的,不能修改,即props.attr的值不能修改,他的值只能父组件中修改,修改后由于props是响应式对象,子组件中的props.attr值会跟着改变。
  2. 如果属性值是原始值类型,则通过props访问该属性返回一个非代理对象。参考props.b和props.bRef。
  3. 如果传入属性值不是原始值类型,并且不是ref和reactive对象,则通过props访问该属性返回的就是传入的值。参考props.a。
  4. 如果传入属性值不是原始值类型,并且是ref或者reactive对象,则通过props访问该属性返回的就是传入的值的reactive对象。参考props.aRef和props.aReactive。
  5. 父组件中修改a的值,子组件中props.a的属性值会着变,有响应性,会重新渲染刷新页面。情况1.
  6. 父组件中修改a的属性值,即修改a.a的值,子组件中props.a、props.aRef、props.aReactive 对应的值会跟着变,但是没有响应性,即不会重新渲染刷新页面。情况2.
  7. 父组件中修改b的值,子组件中只有props.b会跟着变,即props.b有响应性。情况3.
  8. 父组件中修改bRef的值,子组件中只有props.bRef会跟着变,即props.bRef有响应性。情况4
  9. 父组件中修改aReactive的属性值,即修改aReactive.a的值,子组件中props.a、props.aRef、props.aReactive 对应的值会跟着变,有响应性,会重新渲染刷新页面。情况5.

 进一步结论:

  1. props的属性都是有响应性的,即只要传入的属性值的引用(比如:a="a" 中 变量a的值就是一个引用,只有a的值改变才会触发,a.a改变不会触发)发生了改变,即会触发props的属性的响应性,更新对应的属性值。
  2. props.aRef和props.aReactive是响应式对象,通过他们修改对象属性,父组件中对应属性也会跟着改变,并且会重新渲染DOM。

pinia状态的响应式

先看代码:

import { useRootNodeStore } from '@/stores/rootNode';
console.log("useRootNodeStore", useRootNodeStore);//useRootNodeStore 是一个函数
const rootNodeStore = useRootNodeStore();
console.log("rootNodeStore", rootNodeStore);//返回一个reactive对象。

运行结果:

结论:useRootNodeStore是一个函数。useRootNodeStore()结果 就是等价与 reactive(状态定义最后return的对象);比如状态定义如下:

export const useCounterStore = defineStore('counter', () => {const count = ref(0)const doubleCount = computed(() => count.value * 2)function increment() {count.value++}return { count, doubleCount, increment }
})

useRootNodeStore() 返回结果等价于:

reactive({ count, doubleCount, increment })

组合式函数中的响应式

首先看下官方例子:

// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'// 按照惯例,组合式函数名以“use”开头
export function useMouse() {// 被组合式函数封装和管理的状态const x = ref(0)const y = ref(0)// 组合式函数可以随时更改其状态。function update(event) {x.value = event.pageXy.value = event.pageY}// 一个组合式函数也可以挂靠在所属组件的生命周期上// 来启动和卸载副作用onMounted(() => window.addEventListener('mousemove', update))onUnmounted(() => window.removeEventListener('mousemove', update))// 通过返回值暴露所管理的状态return { x, y }
}
<script setup>
import { useMouse } from './mouse.js'const { x, y } = useMouse()
console.log("useMouse", useMouse);//结果是一个函数
console.log("useMouse()", useMouse());//结果是一个reactive对象
</script><template>Mouse position is at: {{ x }}, {{ y }}</template>

结论:useMouse是一个函数。useMouse()结果 就是等价与 reactive({x,y});

组合式函数与pinia状态的区别?

组合式函数主要是为了在多组件中复用,调用函数后,相关与函数体内容在对应组件setup中执行。唯一注意一点区别就是,属性定义不能再函数中定义。const props = defineProps({}) 上面代码不能出现在组合式函数内。

pinia状态管理可以看做一个特殊的组合式函数,pinia状态管理的函数体只有首次调用执行一次,后续在调用直接reactive(之前返回的结果对象)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/19286.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【网络】协议与网络版计算器

协议与网络版计算器 文章目录 1.协议的概念 1.1序列化与反序列化 2.网络版计算器 2.1封装套接字2.2协议定制 2.2.1Jsoncpp2.2.2报文处理 2.3会话层&#xff1a;TcpServer2.4应用层&#xff1a;Calculate2.5表示层&#xff1a;Service2.6应用层、表示层和会话层->应用层 …

C# 添加图标

一、前言 为应用程序添加图标是优化用户界面、提升应用辨识度的重要操作。合适的图标能帮助用户快速识别和区分不同应用&#xff0c;增强应用的易用性和专业性。 本指南旨在为你提供详细、易懂的步骤&#xff0c;教你如何为应用程序的窗体添加图标。从图标素材的获取到具体的…

使用新版本golang项目中goyacc依赖问题的处理

背景 最近项目使用中有用到go mod 和 goyacc工具。goyacc涉及到编译原理的词法分析&#xff0c;文法分析等功能&#xff0c;可以用来生成基于golang的语法分析文件。本期是记录一个使用中遇到的依赖相关的问题。因为用到goyacc&#xff0c;需要生成goyacc的可执行文件。 而项目…

WPS的AI助手进化跟踪(灵犀+插件)

Ver V0.0 250216: 如何给WPS安装插件用以支持其他大模型LLM V0.1 250217: WPS的灵犀AI现在是DeepSeek R1(可能是全参数671B) 前言 WPS也有内置的AI&#xff0c;叫灵犀&#xff0c;之前应是自已的LLM模型&#xff0c;只能说是属于“能用&#xff0c;有好过无”&#xff0c;所…

计算机视觉:卷积神经网络(CNN)基本概念(一)

第一章&#xff1a;计算机视觉中图像的基础认知 第二章&#xff1a;计算机视觉&#xff1a;卷积神经网络(CNN)基本概念(一) 第三章&#xff1a;计算机视觉&#xff1a;卷积神经网络(CNN)基本概念(二) 第四章&#xff1a;搭建一个经典的LeNet5神经网络 一、引言 卷积神经网络&…

rabbitmq详解

有需要的直接看狂神的视频&#xff0c;讲得很好 简介 RabbitMQ 是一个开源的 消息队列中间件&#xff0c;实现了 AMQP&#xff08;Advanced Message Queuing Protocol&#xff0c;先进消息队列协议&#xff09;。它允许 应用程序、服务、系统之间异步地传递消息&#xff0c;并…

moveable 一个可实现前端海报编辑器的 js 库

目录 缘由-胡扯本文实验环境通用流程1.基础移动1.1 基础代码1.1.1 data-* 解释 1.2 操作元素创建1.3 css 修饰1.4 cdn 引入1.5 js 实现元素可移动1.6 图片拖拽2.缩放3.旋转4.裁剪 懒得改文案了&#xff0c;海报编辑器换方案了&#xff0c;如果后面用别的再更。 缘由-胡扯 导火…

计算机视觉中图像的基础认知

第一章&#xff1a;计算机视觉中图像的基础认知 第二章&#xff1a;计算机视觉&#xff1a;卷积神经网络(CNN)基本概念(一) 第三章&#xff1a;计算机视觉&#xff1a;卷积神经网络(CNN)基本概念(二) 第四章&#xff1a;搭建一个经典的LeNet5神经网络 一、图像/视频的基本属性…

java八股文-mysql

1. 索引 1.1 什么是索引 索引(index)是帮助Mysql高效获取数据的数据结构(有序).提高数据的检索效率,降低数据库的IO成本(不需要全表扫描).通过索引列对数据进行排序,降低数据排序成本,降低了CPU的消耗. 1.2 mysql索引使用的B树? 1. 没有使用二叉树&#xff0c;最坏情况o&…

Next.js【详解】CSS 样式方案

全局样式 Global CSS 默认已创建&#xff0c;即 src\app\globals.css&#xff0c;可根据需要修改 默认在全局布局中导入 src\app\layout.tsx import "./globals.css";组件样式 CSS Modules 新建文件 src\app\test\styles.module.css .red {color: red;}导入目标页面…

彻底解决Idea控制台中文乱码问题

中文乱码我相信每一个程序员都会遇到这种问题。 但有时候我们按照网上教程去设置&#xff0c;确实编码好了&#xff0c;但是有时候按照教程来却没能达到我们的预期。 在此之前我将所有编码都设置成了UTF-8&#xff0c;文件编码&#xff0c;项目编码&#xff0c;尝试(最终不需要…

[实现Rpc] 客户端划分 | 框架设计 | common类的实现

目录 3. 客户端模块划分 3.1 Network模块 3.2 Protocol模块 3.3 Dispatcher模块 3.4 Requestor模块 3.5 RpcCaller模块 3.6 Publish-Subscribe模块 3.7 Registry-Discovery模块 3.8 Client模块 4. 框架设计 4.1 抽象层 4.2 具象层 4.3 业务层 ⭕4.4 整体设计框架…

Java里ArrayList和LinkedList有什么区别?

大家好&#xff0c;我是锋哥。今天分享关于【Java里ArrayList和LinkedList有什么区别&#xff1f;】面试题。希望对大家有帮助&#xff1b; Java里ArrayList和LinkedList有什么区别&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 ArrayList 和 LinkedL…

【Java】分布式锁Redis和Redisson

https://blog.csdn.net/weixin_44606481/article/details/134373900 https://www.bilibili.com/video/BV1nW421R7qJ Redis锁机制一般是由 setnx 命令实现&#xff0c;set if not exists&#xff0c;语法setnx key value&#xff0c;将key设置值为value&#xff0c;如果key不存在…

c++TinML转html

cTinML转html 前言解析解释转译html类定义开头html 结果这是最终效果&#xff08;部分&#xff09;&#xff1a; ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6cf6c3e3c821446a84ae542bcc2652d4.png) 前言 在python.tkinter设计标记语言(转译2-html)中提到了将Ti…

2.2 反向传播:神经网络如何“学习“?

一、神经网络就像小学生 想象一个刚学算术的小学生&#xff0c;老师每天布置练习题&#xff0c;学生根据例题尝试解题&#xff0c;老师批改后指出错误。神经网络的学习过程与此相似&#xff1a; 输入层&#xff1a;相当于练习题&#xff08;如数字图片&#xff09;输出层&…

QEMU 通过网络实现共享文件

系列文章目录 Linux内核学习 Linux 知识&#xff08;1&#xff09; Linux 知识&#xff08;2&#xff09; WSL Ubuntu QEMU 虚拟机 Linux 调试视频 PCIe 与 USB 的补充知识 vscode 使用说明 树莓派 4B 指南 设备驱动畅想 Linux内核子系统 Linux 文件系统挂载 QEMU 通过网络实现…

当时只道是寻常

晴&#xff0c;2025年2月16日 卸载了油管、脸书和 X 手机 app &#xff0c;太浪费我时间&#xff0c;以后再去经营吧。 教学技能大赛材料需要在明天之内搞定——《教学实施方案》。感觉玄&#xff0c;同部门有经验的老师说至少花一周时间。 只能明天早点继续接着弄&#xff…

Hive之分区表

Hive之分区表 文章目录 Hive之分区表写在前面分区表分区表基本操作引入分区表创建分区表语法加载数据到分区表中查询分区表中数据增加分区删除分区查看分区表有多少分区查看分区表结构 二级分区正常的加载数据分区表和数据产生关联 动态分区开启动态分区参数设置案例实操 写在前…

【线段树模板】

介绍 这段代码看起来是一个基于树结构的数据结构&#xff0c;可能是线段树或者其他类似的数据结构。主要包含了构建数据结构、查询和修改等基本操作的实现函数。以下是对每个函数的简要介绍&#xff1a; pushup(int u): 用于计算结点u的属性。build(int u, int l, int r): 用于…