提高 React 性能的技巧

news2024/11/17 9:38:30

1.解决重复渲染问题

我们大多数人都知道虚拟 DOM 是如何工作的,但最重要的是检测何时触发树比较。当我们可以跟踪它时,我们可以控制组件的重新渲染,并最终防止意外的性能流。令人惊讶的是,它并不难捕捉。首先,将 React Devtool 扩展添加到浏览器。

  • 然后打开浏览器开发者工具(在 Chrome 中是 Option + ⌘ + J(在 macOS 上),或 Shift + CTRL + J(在 Windows/Linux 上)。

  • 选择组件

  • 点击设置图标

  • 并选中“组件渲染时突出显示更新”

就是这样,现在,当我们与 UI 交互时,它会在当前重新渲染的元素上显示绿色边框。知道了这一点,我们就可以分析我们的任何 React 组件并重构它们以避免不必要的重新渲染。

2.通过拆分组件减少重新渲染

如果我们能够减少意外重新渲染元素的数量,它将解决 React 中的大部分性能问题。

但我们必须首先回答这个问题:“是什么触发了重新渲染?”。答案很简单,状态改变了。

每次组件状态发生变化时,它都会唤醒树比较,也称为协调,并重新呈现状态上下文的元素。

状态上下文,是初始化此类状态的组件。意思是,如果我们有一个巨大的组件,它有很多状态(不需要相互依赖)并且其中一个状态发生了变化,它将重新渲染整个组件元素,这绝对不是我们想要的。

那么,解决方案是什么?解决方法是通过将组件的一部分和它的一些状态移动到它自己的子组件中来分离状态上下文,现在,让我们看一下这个例子:

假设我们有一个带有搜索过滤器的表格组件。搜索过滤器是一个受控输入,其状态在输入文本更改后更新。这是它的样子:

当我们开始在搜索输入字段中输入时会发生什么?

是的,它将重新呈现整个表格元素。发生这种情况是因为输入状态上下文与表组件共享相同的上下文。

现在,让我们尝试我们的解决方案,将输入元素及其状态移动到一个单独的组件中,并将其注入到表格组件中。

神奇的事情发生了,表格组件不再重新渲染。我们稍后可以通过从输入发出事件来控制我们希望输入影响表格元素的确切时间来增强功能。

好的做法是拆分组件以分离状态上下文,以避免冗余的重新渲染。

3.什么是实例重创建,如何避免?

我们已经发现状态更改会触发组件重新渲染,但是我们需要考虑另一个重要的副作用。

当状态改变和协调发生时,它将重新初始化整个组件实例并保持新的状态值。这对我们来说意味着,在协调期间,将重新创建所有函数实例,以便能够考虑新的状态值,我们不需要它,在大多数情况下,函数可以只依赖于几个状态,我们不想重新创建不依赖于已更改状态的函数实例。

这是一个提高性能的机会,我们有几个解决方案:useCallback 和 useRef。让我们看个例子:

const {someState, setSomeState} = useState('')
const {otherState, setOtherState} = useState('')
const foo = () => {console.log(someState)}

这是最常见的例子。我们有依赖于状态 someState 的 foo。当 someState 改变时,它将重新创建 foo 的新实例。

这段代码的问题是,即使其他一些状态发生变化,比如 otherState,foo 也会被重新创建,这是我们实际上不想要的。我们可以使用 useCallback 来告诉 React 我们的函数状态依赖是什么,以便更明确地说明何时重新创建实例:

const {someState, setSomeState} = useState('')
const {otherState, setOtherState} = useState('')
const foo = useCallback(() => {console.log(someState)}, [someState])

在此示例中,我们将依赖项数组传递给 useCallback 挂钩。更好的是,foo 将避免其他状态更改。

另一种选择是使用 useRef。useRef——你可以把它想象成和 useState 一样,但不会触发组件重新渲染(UI 不会更新)。useRef 没有依赖列表,所以我们需要传递 someState as foo 属性:

const {someState, setSomeState} = useState('')
const {otherState, setOtherState} = useState('')
const foo = useRef((currentSomeState) => {console.log(currentSomeState)}).current;

在这种情况下,我们根本不会重新创建 foo 实例。

结论:使用 useCallback 和 useRef 来控制函数实例的重新创建。

4.不要偷懒懒加载

React 默认同步渲染组件。这意味着组件将等到其子项被渲染后再渲染自己。没有必要等待,尤其是当一些子组件没有耦合时。它可能会导致页面挂起。

假设我们点击了一些导航链接,假设将我们重定向到另一个页面。导航将等待所有页面组件呈现完成重定向。它会影响用户体验,人们不会等待,只会离开您的网站。

我们需要使页面内容异步呈现,以免损害导航。解决方案是将您的页面组件包装到 React.lazy(() 并告诉 React 完成导航,然后等待页面组件完成渲染:

const PageComponent = React.lazy(() => import('./PageComponent'));

稍后我们可以使用在页面组件尚未准备好时,显示一些加载动画。

<Suspense fallback={<div>Loading...</div>}>
   <PageComponent />
</Suspense>

这并不意味着我们必须在任何地方都使用 Lazy load 组件,当我们在不会对性能造成太大损害的地方使用它时,它可能会导致过度工程。

另一种场景是一些组件可能默认隐藏在 UI 中,所以我们不必等待它们。例如模态窗口、对话框、抽屉和可折叠的侧面板。

延迟加载页面组件和隐藏的 UI 组件。

5.何时使用 React 片段?

它经常发生,当我们在 JSX 中构建一些布局并想要对我们的元素进行分组时,在大多数情况下我们使用

标签。或者,例如,我们有我们想要移动到单独组件中的父子 HTML 标记:

<ul>
    <li>Item 1</li> <--- | Want to move it to child <Li> |
    <li>Item 2</li> <--- |                               |
</ul>

因此,当我们将 <li> 移动到单独的组件中时,例如:

const Li = () => {
    return (
        <div>
            <li>Item 1</li>
            <li>Item 2</li>
        </div>
    )
}

并改变它:

<ul>
    <Li/>
</ul>

渲染后,它看起来像这样:

<ul>
    <div>
        <li>Item 1</li>  
        <li>Item 2</li>    
    </div>
</ul>

这将创建一个我们不需要的额外<div>节点。

这将使我们的 DOM 树更加嵌套,从而减慢协调过程。

相反, 我们可以将我们的<div>子元素包装到 Fragment 中。

最初,Fragment 允许您对 DOM 元素进行分组,插入后只会导致一次重排。

在 React 中,Fragment 也会让你减少不必要的节点。当你想对元素进行分组时,你唯一需要做的就是使用 Fragment 而不是 <div>:

const Li = () => {
    return (
        <> /* or <React.Fragment>, or <Fragment>*/
            <li>Item 1</li>
            <li>Item 2</li>
        </>
    )
}

就是这样,就这么简单。

如果要对元素进行分组以减少节点数,请使用 Fragment。

6.避免在列出的元素中使用索引作为键

大家都知道,如果没有,Eslint 会强制执行在列出的元素中使用键,例如:

<ul>
    <li key="1">Item 1</li>  
    <li key="2">Item 2</li>    
</ul>

React 中的关键是唯一标识符,它帮助 React 指向列表中的正确元素并更新正确的元素。如果我们使用索引作为列表中的键,比如:

<ul>
    {[1, 2].map((val, index) => <li key={index}>Item {val}</li>)}  
</ul>

我们将元素映射到它的索引。但是如果我们有排序,列表中元素的顺序可能会改变,初始键将不再指向正确的元素。

始终使用唯一 id 作为列出元素的键,如果对象没有它,您可以使用外部库显式分配,如 uid。

7.避免Spread Props

这是今天的最后一个修改调整技巧,已经很多了, 你一定见过,甚至自己亲手做过spreading props。就像是:

const Input = ({ onChange, ...props }) => (
    <input {...props} onChange={e => onChange(e.target.value)}/>
);

它不仅迫使您猜测实际输入接收到的属性是什么,而且还会在输入元素中创建一堆您不一定需要的属性。

让它明确,并且不要害怕根据需要传递尽可能多的属性,您总是可以将它们分组到某个对象中:

const Input = ({ onChange, inputProps: {value, type, className} }) => (
    <input className={className} type={type} value={value} onChange={e => onChange(e.target.value)}/>
);

很好,现在更具可读性。

永远不要spread props,分别传递每个属性。

总结

我想,您可能已经知道 Eslint 强制执行的一些调整,但是现在您知道为什么遵循它们很重要了,而且,您可以对代码进行性能分析,这将为您提供改进空间。

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

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

相关文章

uniapp APP端在线升级功能实现讲解——强制或可选升级,下载进度显示

文章目录 概要 需求分析 技术实现梳理 1.是否更新判断&#xff1a; 2.升级弹窗的展示 3.根据升级类型限制操作 4.下载APP监听下载进度 5.下载完自动安装 核心API讲解 1.plus.downloader.createDownload(url,options,completedCallback)&#xff08;下载&#xff09; 2.plus.r…

使用SOCKET搭建linux和window实现实时摄像头传输(linux传输win端使用C++mfc显示)--Win端开发

1.使用MFC搭建框架 配置: Window10VS2013opencv249 如果VS和opencv配置不一样&#xff0c;让版本对应 Opencv与VS版本 1.1 MFC项目搭建 通过这些步骤就创建了一个MFC基础项目。 1.2项目属性配置 本项目因为要使用opencv,所以就要配置以下opencv的环境 首先在opencv官网下载…

手机连接adb 相关问题汇总

目录 关于端口占用问题1 关于修改adb 端口配置问题2 方法3 方法4 关于端口占用问题1 转载链接&#xff1a;https://www.jianshu.com/p/902a89b06271 报错信息&#xff1a; error: no device/emulators found error: device still connecting 解决方案&#xff1a; 重启…

Day_50小结

目录 一. 比较和分析各种查找算法 二. 描述各种排序算法的特点和基本思想&#xff06;比较分析各种排序算法 1. 插入排序 2. 交换排序 3. 选择排序 4. 外部排序 三. 设计一个自己的 Hash 函数和一个冲突解决机制 1. 对于哈希函数的构造&#xff1a; 2. 处理冲突的办法&#…

Mybatis持久层框架 | Lombok搭建

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Lombok Lombok项目是一个java库&#xff0c;它可以自动插入到编辑器和构建工具中&#xff0c;增强java的性能。不需要再写getter、setter或equals方法&#xff0c;只要…

易语言使用node编译的js文件

环境配置 npm install -g cnpm babel-preset-env babel-cli babel-polyfill browserifynpm install -g crypto-js nodejs转js 例如加密模块 browserify -r babel-polyfill -r crypto-js -o es6.txt browserify file.js -o es6.txt易语言 使用v8 推荐 直接生成导入js即…

LMV331TP-TR 滞后比较器实现精确电压比较与判决

文章目录 LMV331TP-TR1. 滞后比较器的基本概念和作用2. LMV331TP-TR 的特性和规格2.1 工作电压范围2.2 输入电压偏置2.3 响应时间 3. LMV331TP-TR 的工作原理3.1 内部结构3.2 滞后器功能的实现原理 4. 实现准确比较和判决的案例 LMV331TP-TR LMV331TP-TR 是一款优秀的滞后比较…

机器学习之逻辑回归模型

1 逻辑回归模型介绍 逻辑回归(Logistic Regression, LR)又称为逻辑回归分析&#xff0c;是一种机器学习算法&#xff0c;属于分类和预测算法中的一种&#xff0c;主要用于解决二分类问题。逻辑回归通过历史数据的表现对未来结果发生的概率进行预测。例如&#xff0c;我们可以将…

web存储(Storage)

目录 1、基本概念 2、功能监测 2.1 测试可用性 2、W3C标准 3、基本方法或属性 4、 Local Storage 4.1 描述 4.2 示例 5、sessionStorage 5.1 描述 5.2 示例 6、StorageEvent&#xff08;存储事件&#xff09; 6.1 构造函数 6.2 实例属性 6.3 实例方法 6.4 响应…

chatgpt赋能python:Python自动缩进详解

Python 自动缩进详解 作为一门面向对象的高级编程语言&#xff0c;Python 其中一个非常重要的特性便是自动缩进。Python 中的代码块是通过缩进来表示的&#xff0c;而不是通过括号或其他方式。这对于刚开始学习 Python 的初学者来说可能是很困难的&#xff0c;但一旦掌握了这一…

开好会议的方法 会议达成共识 明确目标,促成共识 单向会议 互动会议 会议讨论,文档先行 会前文档 会中 3D法则讨论 同步会议,跟进代办 举个栗子 企业管理

目录 开好会的方法 明确目标&#xff0c;促成共识 单向会议 互动会议 会议讨论&#xff0c;文档先行 会前文档 会中 3D法则讨论 同步会议&#xff0c;跟进代办 举个栗子 开好会的方法 会议有时候时间很长&#xff0c;很多无意义内容&#xff0c;如何开出有意义有价值…

Linux学习之权限表现

groupadd grouptest1添加一个叫grouptest1的用户组。 useradd gooduser -g grouptest1添加一个叫gooduser 的用户&#xff0c;并把它添加到grouptest1用户组里边&#xff0c;id gooduser看一下用户的信息。 接下来进行测试用户和用户组权限。 普通文件 在root账户下&#xf…

AI Chat 设计模式:2. 工厂设计模式

本文是该系列的第二篇&#xff0c;采用问答式的方式展开&#xff0c;问题由我提出&#xff0c;答案由 Chat AI 作出&#xff0c;灰色背景的文字则主要是我的旁白。 问题列表 Q.1 介绍下工厂设计模式A.1Q.2 这种设计模式有哪几种形式A.2Q.3 使用c写一个简单工厂的例子A.3Q.4 我…

操作受限的线性表——队列

本文主要内容&#xff1a;介绍了队列的基本概念和基本操作&#xff0c;详细介绍了队列的顺序存储和链式存储。并介绍了循环队列和双端队列&#xff08;以及输入/输出受限的双端队列&#xff09;&#xff0c;及其基本操作。 目录 队列一、队列的基本概念1、基本概念2、基本操作…

计算机视觉-目标检测(二):从R-FCN到YOLO-v3

文章目录 1. R-FCN1.1 动机1.2. R-FCN 网络结构1.3. R-FCN 的损失函数1.4. R-FCN的训练及性能 2. YoLO-v12.1 简介2.2 YOLO-v1网络结构2.3 目标函数2.4 YOLO-v1的优缺点 3. YOLO-v23.1 YOLO-v2相比v1的优化 4. YOLO-v3参考 1. R-FCN 论文链接&#xff1a;R-FCN:Object Detecti…

java格式化数字 NumberFormat及DecimalFormat

一、JavaAPI官方描述 1、NumberFormat NumberFormat帮助您格式化和解析任何区域设置的数字。您的代码可以完全独立于小数点&#xff0c;千位分隔符的区域设置约定&#xff0c;甚至是使用的特定十进制数字&#xff0c;或者数字格式是否为十进制。 2、 DecimalFormat Decimal…

Linux之YUM管理工具

目录 Linux之YUM管理工具 定义 实现YUM的三个机制 RHEL8中yum源变化说明 案例 示例1 --- 建立本地光盘源&#xff08;本地yum源&#xff09; 示例2 --- 配置互联网源 yum(dnf)工具管理软件包 安装软件包 module子命令 案例 yum-config-manager的使用 定义 yum-conf…

Linux系统如何配置网络

Linux系统的三种网络模式&#xff1a; 桥接&#xff1a;可以和外部设备通信&#xff0c;主机和Ubuntu分别使用不同的IP地址NAT&#xff1a;可以和外部设备通信&#xff0c;主机和Ubuntu公用一个IP地址主机&#xff1a;只能和主机通信 在此我们介绍如何配置桥接网络&#xff1…

谓词的介绍与基本使用

&#x1f6a8;谓词 &#x1f6a6;概念 1.返回类型为bool的仿函数 2.接受一个参数—一元谓词 接受一个参数—二元谓词 &#x1f680;1.一元谓词 ⛽使用方法 因为返回值为bool类型&#xff0c;所以经常会将他使用成判断关系的函数 我们使用find_if&#xff08;&#xff09;对…

HBase 2.3.7中snappy压缩配置

本文将介绍如何在HBase 2.3.7中配置snappy压缩。snappy是一种快速的数据压缩和解压缩算法&#xff0c;可以提高HBase的存储空间利用率和读写性能。本文将使用HBase 2.3.7版本&#xff0c;运行在三个Ubuntu系统的虚拟机中&#xff0c;分别作为master和slave节点。 主要步骤如下…