Immutable-持久化数据结构:助力 React 性能提升

news2024/9/20 0:57:50
  1. React 更新 state 时为什么要使用 Immutable 语法?
  2. Immutable 可持久化数据结构 是什么?
  3. 如何在 React 项目中使用相关 Immutable 类库?

从 useState 说起

状态更新流程

📢 setState(value) React 内部流程:

Diffing
render => VDom
协调 Reconciliation
重新渲染
状态更新请求
状态合并
提交 Commit
  1. 状态更新请求:调用 setState 方法发送状态更新的请求;
  2. 状态合并:React 内部会对状态进行合并,处理多个更新请求;
  3. 重新渲染:根据合并后的状态,重新调用组件的 render 方法生成新的虚拟 DOM;
  4. 协调(Reconciliation):React 会比较新旧虚拟 DOM 树,找出差异 Diffing;
  5. 提交(Commit):将差异应用到实际的 DOM 上,并触发相关生命周期钩子。
🚃 React 的核心目标:尽可能地避免实际 DOM 的更新,因为直接操作 DOM 是昂贵的。
🚎 Diff 算法的目的:识别最小的更新集合,以最高效的方式更新实际的 DOM

如果提供的新值与当前 state 相同(由 Object.is 比较确定),React 将 跳过重新渲染 Render 该组件及其子组件

📡 state 的更新是触发渲染的关键一步!

当然,这也是命中验证缓存命中的关键判断逻辑。

更新 state

🌴 数字、字符串和布尔值,这些类型的值在 JavaScript 中是不可变(immutable)的,通过替换它们的值以触发一次重新渲染。

setTitle('abc')

🌴 对象、数组等,这些类型的值是可变(mutable)的,需要创建新对象。

React 中更新 state 的数据(如:对象),不应该直接修改存放在 React state 中的对象。

obj1 = {a: 1}
obj2 = obj1
obj2.a = 2
Object.is(obj1, obj2) // true (直接跳过渲染,与预期不符!)

可以使用 Object.assign、展开运算符...、深拷贝、concat/slice 等方式来创建新的 state。

setData(prevData => {
  return {
    ...prevData,
    y: 6
  }
})

⚠️ 想要更新一个对象时,需要创建一个新的对象(或者将其拷贝一份),然后将 state 更新为此对象。

不推荐直接修改 state

⚓ 以下来自 React 官方网站1

  • 调试:如果你使用 console.log 并且不直接修改 state,你之前日志中的 state 的值就不会被新的 state 变化所影响。这样你就可以清楚地看到两次渲染之间 state 的值发生了什么变化
  • 优化:React 常见的 优化策略 依赖于如果之前的 props 或者 state 的值和下一次相同就跳过渲染。如果你从未直接修改 state ,那么你就可以很快看到 state 是否发生了变化。如果 prevObj === obj,那么你就可以肯定这个对象内部并没有发生改变。
  • 新功能:我们正在构建的 React 的新功能依赖于 state 被 像快照一样看待 的理念。如果你直接修改 state 的历史版本,可能会影响你使用这些新功能。
  • 需求变更:有些应用功能在不出现任何修改的情况下会更容易实现,比如实现撤销/恢复、展示修改历史,或是允许用户把表单重置成某个之前的值。这是因为你可以把 state 之前的拷贝保存到内存中,并适时对其进行再次使用。如果一开始就用了直接修改 state 的方式,那么后面要实现这样的功能就会变得非常困难。
  • 更简单的实现:React 并不依赖于 mutation ,所以你不需要对对象进行任何特殊操作。它不需要像很多“响应式”的解决方案一样去劫持对象的属性、总是用代理把对象包裹起来,或者在初始化时做其他工作。这也是为什么 React 允许你把任何对象存放在 state 中——不管对象有多大——而不会造成有任何额外的性能或正确性问题的原因。

小结

React 通过浅比较旧的和新的 prop:也就是说,它会考虑每个新的 prop 是否与旧 prop 引用相等。

  • 如果每次父组件重新渲染时创建一个新的对象或数组,即使它们每个元素都相同,React 仍会认为它已更改。
  • 如果在渲染父组件时创建一个新的函数,即使该函数具有相同的定义,React 也会认为它已更改。

👉 这在 性能:React 实战优化技巧 中有提及相关方案,如 React.memouseMemouseCallback 等。

React 性能优化点主要在 ① 减少 DOM 的渲染频次;② 减少 DOM 的渲染范围;③ 非必要的内容延后处理。

🌑 🌒 🌓 🌔 因此,不可变性尤为重要! 🌕 🌖 🌗 🌘

React 使用一种称为虚拟 DOM 的内部机制来维护用户界面的表示。当组件的属性(props)或状态(state)发生变化时,React 会更新虚拟 DOM 以反映这些变化。然后,React 通过比较新旧虚拟 DOM 来执行协调过程,从而决定实际 DOM 中哪些部分需要更新。

这种机制确保只有实际发生变化的部分才会被重新渲染,从而提高性能。然而,有时即使某些 DOM 元素本身没有变化,它们也可能因为其他部分的更改而被迫重新渲染,这是变化部分的副作用。

🚃 Diff 算法的基本原理:在 React 中,每次状态更新时,会生成一个新的虚拟 DOM 树,然后与旧的虚拟 DOM 树进行比较,找出两者之间的差异,这个过程称为“Diff 算法”。
🚎 Diff 算法的复杂性:Diff 算法本质上是一个复杂的过程,尤其是在涉及复杂组件和数据结构时。理想情况下,Diff 算法应该能够快速地识别哪些部分是相同的,哪些部分需要更新。然而,当数据结构可变时,Diff 算法可能需要进行深度遍历,以确定哪些节点或属性发生了变化。

如果组件的属性和状态是不可变的数据结构,可以显著简化 Diff 算法的复杂性。这是因为 Immutable 数据结构的不可变性特性:

🎶 引用比较:由于每次更新都会产生一个完全新的对象,而不是修改原有对象,React 可以简单地通过比较对象的引用,来确定数据是否发生了变化。如果两个对象的引用不同,那么它们肯定不同,这比深度比较数据结构的每个部分要快得多;

🎵结构共享:Immutable 数据结构在进行更新时,只创建变动部分的新实例,而保留未变部分的引用。这意味着在 Diff 算法中,React 能够迅速识别出哪些部分是完全相同的,从而跳过这些部分的比较,直接关注那些确实发生了变化的部分。

可持久化数据结构

可持久化数据结构,是一种能够在修改之后其保留历史版本(即可以在保留原来数据的基础上进行修改——比如增添、删除、赋值)的数据结构。这种数据结构实际上是不可变对象,因为相关操作不会直接修改被保存的数据,而是会在原版本上产生一个新分支。

  • 完全可持久化数据结构:所有版本都可以被查询或修改;
  • 部分可持久化数据结构:所有历史版本都可以被访问,但只有当前版本可以被修改。

可持久化数据结构的实现通常依赖于两个核心概念:版本控制和共享子结构。

  • 版本控制:每当对数据结构进行修改时,都会创建一个新的版本,并保持对旧版本的引用。这样,旧版本的信息得以保留,用户可以通过访问这些版本来追溯历史状态。
  • 共享子结构:在生成新版本时,可持久化数据结构会重用旧版本中未发生变化的部分,即共享子结构。这样可以减少空间复杂度,因为不需要为每个版本单独存储所有数据。

在这里插入图片描述

Immutable

Immutable 实现原理是 可持久化数据结构

使用旧数据创建新数据时,要保证旧数据同时可用且不可变,同时为了避免 deepcopy 的性能损耗,其使用了 结构共享

import update from 'immutability-helper';

let data = {
  a: { msg: 1 },
  b: { msg: 2 },
};
const newData = update(data, { b: { msg: { $set: 3 } } });

console.log(data === newData); 		 // false
console.log(data.a === newData.a); // true
console.log(data.b === newData.b); // false

在 React 项目中使用

export default function Page() {
  const [data, setData] = useState({a: {b: {c: [1, 2]}}});
  /*
   * 希望给 c 追加 3:[1, 2] => [1, 2, 3]
   */
}

① React 原生写法

setData(prevData => ({
 ...prevData, // 展开现有的data对象
 a: {
   ...prevData.a, // 展开data.a
   b: {
     ...prevData.a.b, // 展开data.a.b
     c: [...prevData.a.b.c, 3] // 展开data.a.b.c并追加3
   }
 }
}));

② immutability-helper

import update from 'immutability-helper';
setData(prevData =>
update(prevData, {
  a: {
    b: {
      c: { $push: [3] },
    }
  }
})
);

  1. https://react.docschina.org/learn/updating-objects-in-state#why-is-mutating-state-not-recommended-in-react 为什么在 React 中不推荐直接修改 state ↩︎

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ubuntu下使用docker和socket进行数据交互记录

ubuntu下使用docker和socket进行数据交互记录 概述:主要实现了在宿主机上通过8000端口传递一张图像给docker镜像,然后镜像中处理后,通过8001端口回传处理后的图像给宿主机。 第一章、构建镜像 一、dockerfile文件 1.拉取ubuntu20.04镜像 …

【前端面试】挖掘做过的nextJS项目(下)

https://blog.csdn.net/weixin_43342290/article/details/141170360?spm1001.2014.3001.5501文章浏览阅读105次。需求:快速搭建宣传官网1.适应pc、移动端2.基本的路由跳转3.页面渲染优化4.宣传的图片、视频资源的加载优化5.seo优化全栈react web应用、tailwind css原子工具的支…

Python中的多行字符串和文档字符串

Python中的多行字符串和文档字符串 Python中,多行字符串和文档字符串都使用三引号(""" 或 )来定义。都可以跨越多行而不需要使用行连接符(\)。 多行字符串和文档字符串都可以利用转义符来调整格式——…

vue3.0脚手架、路由、Element Plus安装案例:收录于Vue 3.0 后台管理系统案例

目录 环境配置 Vue 3.0 脚手架(Vite)安装 node版本查询与切换 创建一个vue应用 Vue Router安装 安装vue-router4 配置路由 安装配置 展示路由 Element UI安装 安装element-plus 引入element-plus 使用element-plus 用户登录 环境配置 Vue 3…

HarmonyOS(51) 应用沙箱目录和Context获取文件路径

文件目录 应用沙箱目录沙箱目录的分类应用文件目录结构应用文件路径详细说明ApplicationContext获取应用文件路径通过AbilityStageContext、UIAbilityContext、ExtensionContext获取HAP级别的应用文件路径切换el1和el2AreaMode简介 参考资料 应用沙箱目录 沙箱目录的分类 如下…

CUDA+tensorflow+python+vscode在GPU下环境安装及问题汇总与解答

2024.8.14 因为要做深度学习,需要安装tensorflowgpu的环境,每次都搞不好整的很生气,本次将安装过程中参考的一些大佬的博客和安装过程中遇到的问题及解决方案总结一下,希望以后不要在这件事情上浪费时间。安装环境其实也没有想象中…

迁移学习代码复现

一、前言 说来可能令人难以置信,迁移学习技术在实践中是非常简单的,我们仅需要保留训练好的神经网络整体或者部分网络,再在使用迁移学习的情况下把保留的模型重新加载到内存中,就完成了迁移的过程。之后,我们就可以像训练普通神经网络那样训练迁移过来的神经网络了。 我们…

浅谈SIMD、向量化处理及其在StarRocks中的应用

前言 单指令流多数据流(SIMD)及其衍生出来的向量化处理技术已经有了相当的历史,并且也是高性能数据库、计算引擎、多媒体库等组件的标配利器。笔者在两年多前曾经做过一次有关该主题的内部Geek分享,但可能是由于这个topic离实际研发场景比较远&#xff0…

使用大模型从政府公文中抽取指标数据

文章目录 介绍流程结构介绍相关文本筛选大模型 few-shot大模型抽取结果 介绍 本文使用LangChain 结合 Ollama的qwen2:7b模型,抽取出全国市级单位每一年预期生产总值指标。 Ollama的qwen2:7b,显存占用只有5G左右,适合大多数消费级显卡运行。…

华为云Api调用怎么生成Authorization鉴权信息,StringToSign拼接流程

请求示例 Authorization 为了安全,华为云的 Api 调用都是需要在请求的 Header 中携带 Authorization 鉴权的,这个鉴权15分钟内有效,超过15分钟就不能用了,而且是需要调用方自己手动拼接的。 Authorization的格式为 OBS 用户AK:…

zabbix agent 可用性 为 灰色

解决zabbix可用性为灰色状态 配置–》模板–》选择模板, 之后正常。

排序: 插入\希尔\选择\归并\冒泡\快速\堆排序实现

1.排序的概念及应用 1.1概念 排序:所谓排序,就是一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。 1.2运用 购物筛选排序: 1.3常见排序算法 2.实现常见的排序算法 int a[ {5,3,9,6,2,4,7,1,8}; 2…

MySQL数据库专栏(三)数据库服务维护操作

1、界面维护,打开服务窗口找到MySQL服务,右键单击可对服务进行启动、停止、重启等操作。 选择属性,还可以设置启动类型为自动、手动、禁用。 2、指令维护 卸载服务:sc delete [服务名称] 例如:sc delete MySQL 启动服…

嵌入式软件开发学习一:软件安装(保姆级教程)

资源下载: 江协科技提供: 资料下载 一、安装Keil5 MDK 1、双击.EXE文件,开始安装 2、 3、 4、此处尽量不要安装在C盘,安装路径选择纯英文,防止后续开发报错 5、 6、 7、弹出来的窗口全部关闭,进入下一步&a…

STM32(一):新建工程

stm32f10x.h文件:描述stm32有哪些寄存器(外围)和它对应的地址。stm32由内核和内核外围的设备组成的,内核寄存器描述和外围寄存器描述文件存储位置不在一起core_cm3.h core_cm3.c内核寄存器描述文件。mic.c内核库函数 stm32f10x_co…

【初阶数据结构】通讯录项目(可用作课程设计)

文章目录 概述1. 通讯录的效果2. SeqList.h3. Contact.h4. SeqList.c5. Contact.c6. test.c 概述 通讯录项目是基于顺序表这个数据结构来实现的。如果说数组是苍蝇小馆,顺序表是米其林的话,那么通讯录就是国宴。 换句话说,通讯录就是顺序表…

pycharm windows/mac 指定多版本python

一、背景 工作中经常会使用不同版本的包,如同时需要tf2和tf1,比较新的tf2需要更高的python版本才能安装,而像tf1.5 需要低版本的python 才能安装(如 python3.6),所以需要同时安装多个版本。 二、安装多版本python py…

会员系统开发,检测按钮位置,按钮坐标,弹出指定位置对话框-SAAS 本地化及未来之窗行业应用跨平台架构

一 获取元素坐标 var 按钮_obj document.querySelector(#未来之窗玄武id);var 按钮_rect 按钮_obj.getBoundingClientRect()console.log(按钮_rect);输出结果 bottom : 35 height : 21 left : 219.921875 right : 339.921875 top : 14 width : 120 x : 219.921875 y…

R语言统计分析——组间差异的非参数检验

参考资料:R语言实战【第2版】 如果数据无法满足t检验或ANOVA的参数假设,可以转而使用非参数检验。举例来说,若结果变量在本质上就严重偏倚或呈现有序关系,那么可以考虑非参数检验。 1、两组的比较 若两组数据独立,可以…

大厂进阶五:React源码解析之Diff算法

本文主要针对React源码进行解析,内容有: 1、Diff算法原理、两次遍历 一、Diff源码解析 以下是关于 React Diff 算法的详细解析及实例: 1、React Diff 算法的基本概念和重要性 1.1 概念 React Diff 算法是 React 用于比较虚拟 DOM 树之间…