当前位置: 首页 > news >正文

React组件性能优化实践

React组件性能优化最佳实践

React组件性能优化的核心是减少渲染真实DOM节点的频率,减少 Virtual DOM比对的频率。

组件卸载前进行清理操作

在组件中为 window注册的全局事件,以及定时器,在组件卸载前要清理掉,防止组件卸载后继续执行影响应用性能。

需求:开启定时器,然后卸载组件,查看组件中的定时器是否还在运行。

function Test(){useEffect(()=>{let timer = setInterval(()=>{console.log("定时器在执行");},1000);return ()=>clearInterval(timer);},[]);
}

通过纯组件提升组件性能(类组件)

  1. 什么是纯组件?

纯组件会对组件输入数据进行浅层比较,如果当前输入数据和上次输入数据相同,组件不会重新渲染。

  1. 什么是浅层比较

比较引用数据类型在内存中的引用地址是否相同,比较基本数据类型的值是否相同

  1. 如何实现纯组件

类组件继承 PureComponent 类,函数组件使用 memo 方法

  1. 为什么不直接进行 diff 操作,而是要先进行浅层比较,浅层比较难道没有性能消耗吗

和进行 diff 比较操作相比,浅层比较将消耗更少的性能。diff 操作会重新遍历整棵 virtualDOM 树,而浅层比较只操作当前组件的 state 和 props。

  1. 需求:在状态对象中存储 name 值为张三,组件挂载完成后将 name 属性的值再次更改为张三,然后分别将 name 传递给纯组件和非纯组件,查看结果。
class App extends Component{constructor(){super();this.state = {name: "张三"}}updateName(){setInterval(()=>{this.setState({name:"张三"});},1000);}componentDidMount(){this.updateName();}render(){return <><ReguarComponent name={this.state.name} /><PureComponentDemo name={this.state.name} /></>}
}class ReguarComponent extends Component{render(){console.log('ReguarComponent');return <div>{this.props.name}</div>}
}
class PureComponentDemo extends PureComponent{render(){console.log('PureComponentDemo');return <div>{this.props.name}</div>}
}

通过 shouldComponentUpdate 生命周期函数提升组件性能

纯组件只能进行浅层比较,要进行深层比较,使用 shouldComponentUpdate,它用于编写自定义比较逻辑。返回 true 重新渲染组件,返回 false 阻止重新渲染。

函数的第一个参数微 nextProps,第二个参数为 nextState。

需求:在页面中展示员工信息,员工信息包括:姓名、年龄、职位,但是在页面中只想展示姓名和年龄,也就是说只有姓名和年龄发生变化时才有必要重新渲染组件,如果员工的其他信息发生了变化没必要重新渲染组件。

class App extends Component{constructor(){super()this.state ={person: {name: "张三",age: 20,job: "waiter"}}}componentDidMount(){setTimeout(()=>{this.setState({person:{...this.state.person,job:"chef"}});},2000);}shouldComponentUpdate(nextProps,nextState){if(nextState.person.name!==this.state.person.name||nextState.person.age!==this.state.person.age){return true}return false}render(){console.log('.....render....');return <div>{this.state.person.name}{this.state.person.age}</div>}
}

通过纯组件提升组件性能(函数组件)

将函数组件变为纯组件,将当前 props 和上一次的 props 进行浅层比较,如果相同就阻止组件重新渲染。

需求:父组件维护两个状态,index 和 name,开启定时器让 index 不断发生变化,name 传递给子组件,查看父组件更新子组件是否也更新了。

function App(){const [name] = useState("张三");const [index,setIndex] = useState(0);useEffect(()=>{setInterval(()=>{setIndex(prev=>prev+1)},1000)},[];return (<div>{index}<ShowName name={name}/></div>)
}
const ShowName = memo(function showName({name}){return <div>{name}</div>
})

使用 memo方法自定义比较逻辑,用于执行深层比较。比较函数的第一个参数为上一次的 props,比较函数的第二个参数为下一次的 props,比较函数返回 true,不进行渲染,比较函数返回 false,组件重新渲染。

function comparePerson(prevProps,nextProps){if(prevProps.person.name!==nextProps.person.name||prevProps.person.age!==nextProps.person.age){return false;}return true;
}
const ShowPersonMemo = memo(ShowPerson,comparePerson);

使用组件懒加载

使用组件懒加载可以减少 bundle 文件大小,加快组件呈递速度。

  1. 路由组件懒加载
import React,{lazy,Suspense} from "react";
import {BrowserRouter,Link,Route,Switch} from "react-router-dom";const Home =lazy(()=>import("./Home"))
const List = lazy(()=>import("./List"));function App(){return (<BrowserRouter><Link to="/">Home</Link><Link to="/list">List</Link><Switch><Suspense fallback={<div>Loading</div>}><Route path="/" component={Home} exact/><Route path="/list" component={List}/></Suspense></Switch></BrowserRouter>)
}
  1. 根据条件进行组件懒加载,适用于组件不会随条件频繁切换
function App(){let LazyComponent = null;if(true){LazyComponent = lazy(()=>import("./Home"));}else{LazyComponent = lazy(()=>import("./List"));}return (<Suspense fallback={<div>Loading</div>}><LazyComponent /></Suspense>);
}

使用占位符标记提升 React 组件的性能

React 组件中返回的 jsx 如果有多个同级元素,多个同级元素必须要有一个共同的父级。

function App(){return (<div><div>message a</div><div>message b</div></div>)
}

为了满足这个条件我们通常都会在最外层添加一个 div,但是这样的话就会多出一个无意义的标记,如果每个组件都多出这样的一个无意义标记的话,浏览器渲染引擎的负担就会加剧。

为了解决这个问题,React 推出了 fragment 占位符标记,使用占位符标记既满足了拥有共同父级的要求又不会多出额外的无意义标记。

import {Fragment} from "react"function App(){return (<Fragment><div>message a</div><div>message b</div></Fragment>)
}

如果你觉得 Fragment 比较烦琐也可以写空占位符。

function App(){return (<><div>message a</div><div>message b</div></>)
}

避免使用内联函数

在使用内联函数后,render 方法每次运行时都会创建该函数的新实例,导致 React 在进行 Virtual DOM 比对时,新旧函数比对不想等,导致 React 总是为元素绑定新的函数实例,而旧的函数实例又要交给垃圾回收器处理。

class App extends React.Component{constructor(){super()this.state = {inputValue: ""}}render(){return(<input value={this.state.inputValue} onChange={e=>this.setState({inputValue:e.target.value})}/>)}
}

正确的做法是在组件中单独定义函数,将函数绑定给事件。

class App extends React.Component{constructor(){super()this.state = {inputValue: ""}}setInputValue=e=>{this.setState({inputValue:e.target.value})}render(){return(<input value={this.state.inputValue} onChange={this.setInputValue}/>)}
}

在构造函数中进行函数 this 绑定

在类组件中如果使用 fn(){}这种方式定义函数,函数 this 默认指向 undefined。也就是说函数内部的 this 指向需要被更正。可以在构造函数中对函数的 this 进行更正。也可以在行内进行更正。两者看起来没有太大区别,但是对性能的影响是不同的。

class App extends React.Component{constructor(){super()//方式一//构造函数只执行一次,所以函数 this 指向更正的代码也只执行一次this.handleClick = this.handleClick.bind(this)}handleClick(){console.log(this)}render(){//方式二//问题:render 方法每次执行时都会调用 bind 方法生成新的函数实例return <button onClick={this.handleClick.bind(this)}>按钮</button>}
}

类组件中的箭头函数

在类组件中使用箭头函数不会存在 this 指向问题,因为箭头函数本身并不绑定 this。

class App extends React.Component{handleClick = ()=>console.log(this)render(){return <button onClick={this.handleClick}>按钮</button>}
}

箭头函数在 this 指向问题上占据优势,但是同时也有不利的一面。

当使用箭头函数时,该函数被添加为类的实例对象属性,而不是原型对象属性,如果组件被多次重用,每个组件实例对象中都将会有一个相同的函数实例,降低了函数实例的可重用性造成了资源浪费。

综上所述,更正函数内部 this指向的最佳做法仍是在构造函数中使用 bind 方法进行绑定。

避免使用内联样式属性

当使用内敛 style 为元素添加样式时,内联 style 会被编译为 JavaScript 代码,通过 JavaScript 代码将样式规则映射到元素的身上,浏览器就会花费更多的时间执行脚本和渲染 UI,从而增加了组件的渲染时间。

function App(){return <div style={{backgroundColor:"skyblue"}}>App works</div>
}

在上面的组件中,为元素附加了内联样式,添加的内联样式为 JavaScript 对象,backgroundColor 需要被转换为等效的 CSS 样式规则,然后将其应用到元素,这样涉及到脚本的执行。

更好的办法是将 CSS 文件导入样式组件,能通过 CSS 直接做的事情就不要通过 JavaScript 去做,因为 JavaScript 操作 DOM 非常慢。

优化条件渲染

频繁的挂载和卸载组件是一项耗性能的操作,为了确保应用程序的性能,应该减少组件挂载和卸载的次数。在 React 中我们经常会根据条件渲染不同的组件,条件渲染是一项必做的优化操作。

function App(){if(true){return (<><AdminHeader/><Header /><Content /></>)}else{return (<><Header /><Content /></>)}
}

在上面的代码中,当渲染条件发生变化时,React 内部在做 Virtual DOM 比对时发现,刚刚第一个组件是 AdminHeader,现在第一个组件是 Header,刚刚第二个组件是 Header,现在第二个组件是 Content,组件发生了变化,React 就会卸载 AdminHeader、Header、Content,重新挂载 Header 和 Content,这种挂载和卸载就是没有必要的。

function App(){return (<>{true&&<AdminHeader />}<Header /><Content /></>)
}

避免重复的无限渲染

当应用程序状态发生更改时,React 会调用 render 方法。如果在 render 方法中继续更改应用程序状态,就会发生 render 方法递归调用导致应用报错。

class App extends React.Components{constructor(){super()this.state={name:"张三"}}render(){this.setState({name:"李四"})return <div>{this.state.name}</div>}
}

与其他生命周期函数不同,render 方法应该被作为纯函数。这意味着,在 render 方法中不要做以下事情,比如不要调用 setState 方法,不要使用其他手段查询更改原生 DOM 元素,以及其他更改应用程序的任何操作。render 方法的执行要根据状态的改变,这样可以保持组件的行为和渲染方式一致。

为组件创建错误边界

默认情况下,组件渲染错误会导致整个应用程序中断,创建错误边界可确保在特定组件发生错误时应用程序不会中断。

错误边界是一个 React 组件,可以捕获子级组件在渲染时发生的错误,当错误发生时,可以将错误记录下来,可以显示备用 UI 界面。错误边界涉及到两个生命周期函数,分别是 getDerivedStateFromError 和 componentDidCatch。getDerivedStateFromError为静态方法,方法中需要返回一个对象,该对象会和 state 对象进行合并,用于更改应用程序状态。componentDidCatch方法用于记录应用程序错误信息,该方法的参数就是错误对象。

class ErrorBoundaries extends React.Component{constructor(){super()this.state={hasError:false}}componentDidCatch(error){console.log("componentDidCatch")}static getDerivedStateFromError(){console.log("getDerivedStateFromError");return {hasError: true}}render(){if(this.state.hasError){return <div>发生了错误</div>}return <App />}
}
ReactDom.render(<ErrorBoundaries />,document.getElementById("root"))

注意:错误边界不能捕获异步错误,比如点击按钮时发生的错误。

避免数据结构突变

组件中 props 和 state 的数据结构应该保持一致,数据结构突变会导致输出不一致。

class App extends Component{constructor(){super()this.state = {employee:{name: "张三",age: 20}}}render(){const {name,age} = this.state.employeereturn (<div>{name}{age}<button onClick={()=>this.setState({...this.state,employee:{age:30}})}>change age</button></div>)}
}

优化依赖项大小

在应用程序中经常会依赖第三方包,但我们不想引用包中的所有代码,我们只想用到哪些代码就包含哪些代码。此时可以使用插件对依赖项进行优化。

相关文章:

React组件性能优化实践

React组件性能优化最佳实践 React组件性能优化的核心是减少渲染真实DOM节点的频率&#xff0c;减少 Virtual DOM比对的频率。 组件卸载前进行清理操作 在组件中为 window注册的全局事件&#xff0c;以及定时器&#xff0c;在组件卸载前要清理掉&#xff0c;防止组件卸载后继…...

SpringBoot复习:(29)静态资源的配置路径

WebMvcAutoConfiguration 首页处理&#xff1a;...

mysql延时问题排查

背景介绍 最近遇到一个奇怪的问题&#xff0c;有个业务&#xff0c;每天早上七点半产生主从延时&#xff0c;延时时间12.6K&#xff1b; 期间没有抽数/备份等任务&#xff1b;查看慢日志发现&#xff0c;期间有一个delete任务&#xff0c;在主库执行了161s delete from xxxx_…...

接口设置了responseType:‘blob‘后,接收不到后端错误信息

下载文件流&#xff0c;需要接口设置responseType:blob&#xff0c;接口设置了responseType:blob后&#xff0c;拿不到后端接口的异常信息&#xff0c;我们只需要添加如下代码&#xff1a; const service axios.create({baseURL: ***, // url base url request url// withC…...

无涯教程-Perl - mkdir函数

描述 此功能使用MODE指定的模式创建一个名称和路径EXPR的目录,为清楚起见,应将其作为八进制值提供。 语法 以下是此函数的简单语法- mkdir EXPR,MODE返回值 如果失败,此函数返回0,如果成功,则返回1。 例 以下是显示其基本用法的示例代码- #!/usr/bin/perl -w$dirname &…...

css3 瀑布流布局遇见截断下一列展示后半截现象

css3 瀑布流布局遇见截断下一列展示后半截现象 注&#xff1a;css3实现瀑布流布局简直不要太香&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e; 场景-在uniapp项目中 当瀑布流布局column-grap:10px 相邻两列之间的间隙为10px&#xff0c;column-count:2,2列展…...

C++初阶之一篇文章教会你list(理解和使用)

list&#xff08;理解和使用&#xff09; 什么是list特点和优势基本操作示例用法与其他序列式容器&#xff08;如 std::vector 和 std::deque&#xff09;相比&#xff0c;std::list 显著的区别和优势成员类型 list构造函数1. default (1)2. fill (2)3.range (3)4. copy (4) li…...

如何给Linux开启swap虚拟内存

查看系统内存资源 free -h 创建swap分区 dd if/dev/zero of/swapfile bs1024 count4194304dev/zero&#xff1a;是Linux的一种特殊字符设备(输入设备)&#xff0c;可以用来创建一个指定长度用于初始化的空文件&#xff0c;如临时交换文件&#xff0c;该设备无穷尽地提供0&…...

spring按条件注入@Condition及springboot对其的扩展

概述 spring的ioc极大的方便了日常开发&#xff0c;但随着业务的迭代。配置的一些参数在某些情况下需要按条件注入。 比如原先定义的db公共模块下&#xff0c;相关的配置和工具类只是基于mysql的。但是后续有模块需要使用mongo/es等其他数据库&#xff0c;又想继续使用db公共…...

MySQL多表连接查询3

目录 表结构 创建表 表数据 查询需求&#xff1a; 1.查询student表的所有记录 2.查询student表的第2条到4条记录 3.从student表查询所有学生的学号&#xff08;id&#xff09;、姓名&#xff08;name&#xff09;和院系&#xff08;department&#xff09;的信息 4.从s…...

【从零开始学习JAVA | 第四十五篇】反射

目录 前言&#xff1a; ​反射&#xff1a; 使用反射的步骤&#xff1a; 1.获取阶段&#xff1a; 2.使用阶段&#xff1a; 反射的应用场景&#xff1a; 使用反射的优缺点&#xff1a; 总结&#xff1a; 前言&#xff1a; Java中的反射是一项强大而灵活的功能&#xff0…...

顺丰科技数据治理实践

01 顺丰数据治理体系演进路线 顺丰做数据治理十多年&#xff0c;数据治理体系的模块是逐步来建设的。十年前&#xff0c;我们就已经建了数仓&#xff0c;同步做了元数据管理&#xff0c;数据质量管理&#xff0c;以及数据安全的管理。顺丰数据治理的演进路线分 3 个阶段。 第…...

Nginx+Tomcat负载均衡、动静分离实例详细部署

一、反向代理两种模式 四层反向代理 基于四层的iptcp/upd端口的代理 他是http块同一级&#xff0c;一般配置在http块上面。 他是需要用到stream模块的&#xff0c;一般四层里面没有自带&#xff0c;需要编译安装一下。并在stream模块里面添加upstream 服务器名称&#xff0c;…...

Java多线程(3)---锁策略、CAS和JUC

目录 前言 一.锁策略 1.1乐观锁和悲观锁 ⭐ 两者的概念 ⭐实现方法 1.2读写锁 ⭐概念 ⭐实现方法 1.3重量级锁和轻量级锁 1.4自旋锁和挂起等待锁 ⭐概念 ⭐代码实现 1.5公平锁和非公平锁 1.6可重入锁和不可重入锁 二.CAS 2.1为什么需要CAS 2.2CAS是什么 ⭐CAS…...

Linux:Shell编辑之文本处理器(awk)

目录 绪论 1、用法 1.1 格式选项 1.2 awk 常用内置变量 1.3 awk的打印功能 1.4 奇偶打印 1.5 awk运算 1.6 awk的内置函数&#xff1a;getline 1.7 文本过滤打印 1.8 awk条件判断打印 1.9 三元表达式&#xff0c;类似于java 1.10 awk的精确筛选 1.11 awk和tr比较改变…...

探索FSM (有限状态机)应用

有限状态机&#xff08;FSM&#xff09; 是计算机科学中的一种数学模型&#xff0c;可用于表示和控制系统的行为。它由一组状态以及定义在这些状态上的转换函数组成。FSM 被广泛用于计算机程序中的状态机制。 有限状态机&#xff08;FSM&#xff09;应用场景 在各种自动化系统…...

6.continue break

6.1continue 关键字 continue 关键字用于立即跳出本次循环&#xff0c;继续下一次循环&#xff08;本次循环体continue之后的代码会少执行一次&#xff09;。 例如&#xff1a;吃5个包子&#xff0c;第3个有虫子&#xff0c;就扔掉第3个包子&#xff0c;继续吃第4个第5个包子…...

如何在Linux中强制关闭卡住的PyCharm

在使用PyCharm进行Python开发时&#xff0c;有时可能会遇到卡顿或无响应的情况。当PyCharm卡住时&#xff0c;我们需要强制关闭它以恢复正常操作。今天&#xff0c;我们将介绍在Linux系统中如何强制关闭PyCharm的几种方法。 1. 使用键盘快捷键 在PyCharm所在的窗口中&#xf…...

c# Excel数据的导出与导入

搬运:Datagrideview 数据导出Excel , Exel数据导入 //------------------------------------------------------------------------------------- // All Rights Reserved , Copyright (C) 2013 , DZD , Ltd . //----------------------------------------------------------…...

Kotlin~Mediator中介者模式

概念 创建一个中介来降低对象之间的耦合度&#xff0c;关系”多对多“变为“一对多”。 角色介绍 Mediator&#xff1a;抽象中介者&#xff0c;接口或者抽象类。ConcreteMediator&#xff1a;中介者具体实现&#xff0c;实现中介者接口&#xff0c;定义一个List管理Colleagu…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API&#xff0c;用于在函数组件中使用 state 和其他 React 特性&#xff08;例如生命周期方法、context 等&#xff09;。Hooks 通过简洁的函数接口&#xff0c;解决了状态与 UI 的高度解耦&#xff0c;通过函数式编程范式实现更灵活 Rea…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…...

超短脉冲激光自聚焦效应

前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应&#xff0c;这是一种非线性光学现象&#xff0c;主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场&#xff0c;对材料产生非线性响应&#xff0c;可能…...

多场景 OkHttpClient 管理器 - Android 网络通信解决方案

下面是一个完整的 Android 实现&#xff0c;展示如何创建和管理多个 OkHttpClient 实例&#xff0c;分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

微信小程序云开发平台MySQL的连接方式

注&#xff1a;微信小程序云开发平台指的是腾讯云开发 先给结论&#xff1a;微信小程序云开发平台的MySQL&#xff0c;无法通过获取数据库连接信息的方式进行连接&#xff0c;连接只能通过云开发的SDK连接&#xff0c;具体要参考官方文档&#xff1a; 为什么&#xff1f; 因为…...

服务器--宝塔命令

一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行&#xff01; sudo su - 1. CentOS 系统&#xff1a; yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...

Mysql中select查询语句的执行过程

目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析&#xff08;Parser&#xff09; 2.4、执行sql 1. 预处理&#xff08;Preprocessor&#xff09; 2. 查询优化器&#xff08;Optimizer&#xff09; 3. 执行器…...

CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)

漏洞概览 漏洞名称&#xff1a;Apache Flink REST API 任意文件读取漏洞CVE编号&#xff1a;CVE-2020-17519CVSS评分&#xff1a;7.5影响版本&#xff1a;Apache Flink 1.11.0、1.11.1、1.11.2修复版本&#xff1a;≥ 1.11.3 或 ≥ 1.12.0漏洞类型&#xff1a;路径遍历&#x…...

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要&#xff1a; 近期&#xff0c;在使用较新版本的OpenSSH客户端连接老旧SSH服务器时&#xff0c;会遇到 "no matching key exchange method found"​, "n…...