前端宝典之七:React性能优化实战精华篇
本文主要讲解实战项目中React性能优化的方法,主要分为三个大的方面:减少不必要的组件更新、组件优化以及tree-shaking,共11个方法
一、减少不必要组件更新
以下是一些可以避免在 React 提交阶段进行不必要重新渲染的方法:
1、使用 React.memo
(对于函数组件)和 PureComponent
(对于类组件)
-
React.memo
:
React.memo
是一个高阶组件,用于包装函数组件。它通过对组件的props
进行浅层比较来决定是否重新渲染组件。
示例:import React from 'react';const MyComponent = React.memo(({ data }) => {// 组件渲染逻辑return <div>{data}</div>; });
当
data
的引用没有发生变化时,组件将不会重新渲染。 -
PureComponent
(对于类组件):
PureComponent
会对props
和state
进行浅层比较。如果它们没有变化,组件将不会重新渲染。
示例:
以下是一个在类组件中使用PureComponent
的示例,包括数据传递和更新:
import React, { PureComponent } from 'react';class MyComponent extends PureComponent {// 构造函数,初始化状态constructor(props) {super(props);this.state = {count: 0,name: 'Initial Name',};}// 处理点击事件,更新状态handleClick = () => {// 示例 1:更新数字状态this.setState({ count: this.state.count + 1 });// 示例 2:更新字符串状态(如果 name 是从父组件传递的 props 且未变化,不会触发重新渲染)// 假设 name 是从父组件传递的 props,以下更新不会触发重新渲染(如果 name 未变化)// this.setState({ name: this.props.name });};render() {return (<div><p>Count: {this.state.count}</p><p>Name: {this.state.name}</p><button onClick={this.handleClick}>Increment Count</button></div>);}
}// 父组件
class ParentComponent extends React.Component {constructor(props) {super(props);this.state = {name: 'Parent Name',};}handleNameChange = () => {this.setState({ name: 'Updated Name' });};render() {return (<div><MyComponent name={this.state.name} /><button onClick={this.handleNameChange}>Change Name</button></div>);}
}export default ParentComponent;
在这个例子中:
-
MyComponent
是一个继承自PureComponent
的类组件。它有一个count
状态用于数字的递增展示,还有一个name
状态(也可以是从父组件传递的props
)用于展示字符串。 -
在
render
方法中,展示了count
和name
的值,并有一个按钮用于触发count
的递增。 -
ParentComponent
是父组件,它有一个name
状态,并将其传递给MyComponent
。还有一个按钮用于更改name
的状态。
PureComponent
会对 props
和 state
进行浅层比较。如果 props
或 state
的引用没有变化,组件将不会重新渲染。在上面的例子中,如果 MyComponent
接收到的 props.name
没有变化,并且 state
中的 count
没有更新,MyComponent
就不会重新渲染。
注意事项:
PureComponent
的浅层比较对于基本数据类型(如数字、字符串、布尔值)是有效的,但对于复杂数据类型(如对象、数组),它只会比较引用。如果对象或数组的内容发生变化,但引用不变,PureComponent
可能不会检测到变化。在这种情况下,可以使用immutable.js
或手动在shouldComponentUpdate
中进行深层比较。- 如果组件的
props
或state
变化频繁且计算成本不高,或者需要进行深层比较,可能不需要使用PureComponent
。
2、使用 useCallback
和 useMemo
-
useCallback
:
useCallback
用于记忆函数,确保传递给子组件的函数在依赖项不变的情况下不会重新创建。
示例:import React, { useState, useCallback } from 'react';function ParentComponent() {const [count, setCount] = useState(0);const handleClick = useCallback(() => {// 处理点击的逻辑}, [count]); // 仅当 count 变化时重新创建函数return (<div><ChildComponent onClick={handleClick} /></div>); }
-
useMemo
:
useMemo
用于记忆计算结果,避免在每次渲染时都进行昂贵的计算。
示例:import React, { useState, useMemo } from 'react';function MyComponent() {const [data, setData] = useState([]);const computedValue = useMemo(() => {// 进行昂贵的计算return data.map((item) => item * 2);}, [data]);return <div>{computedValue}</div>; }
3、优化 shouldComponentUpdate
(对于类组件)
在类组件中,可以重写 shouldComponentUpdate
方法来进行更细粒度的控制。
import React from 'react';class MyComponent extends React.Component {shouldComponentUpdate(nextProps, nextState) {// 进行 props 和 state 的比较,决定是否更新return (nextProps.someValue!== this.props.someValue ||nextState.someState!== this.state.someState);}render() {return <div>{/*... */}</div>;}
}
4、避免在渲染阶段进行副作用操作
副作用操作(如网络请求、订阅事件等)应该在 useEffect
中进行,而不是在组件的渲染函数中。这样可以确保渲染函数的纯粹性,减少不必要的重新渲染触发。
import React, { useState, useEffect } from 'react';function MyComponent() {const [data, setData] = useState(null);useEffect(() => {// 进行网络请求获取数据fetchData().then((result) => setData(result));}, []); // 空依赖数组确保只在组件挂载时执行一次return <div>{data? data : 'Loading...'}</div>;
}
5、正确设置 key
属性(对于列表渲染)
- 在渲染列表时,为每个列表项设置唯一的
key
属性。这有助于 React 更高效地识别和更新列表项。import React from 'react';function ListComponent({ items }) {return (<ul>{items.map((item) => (<li key={item.id}>{item.name}</li>))}</ul>); }
二、组件优化
1、useIntersectionObserver
在 React 项目中使用 TypeScript 和 useIntersectionObserver
实现虚拟滚动懒加载的示例代码:
import React, { useEffect, useRef } from 'react';function LazyLoadComponent() {const imageRefs = useRef<HTMLDivElement[]>([]);const observerRef = useRef<IntersectionObserver | null>(null);useEffect(() => {const options = {root: null,rootMargin: '0px',threshold: 0.1,};observerRef.current = new IntersectionObserver((entries) => {entries.forEach((entry) => {if (entry.isIntersecting) {// 这里可以进行实际的图片加载或其他数据加载逻辑const index = imageRefs.current.findIndex((ref) => ref === entry.target);console.log(`图片 ${index + 1} 进入可视区域`);// 加载完成后可以停止观察该元素observerRef.current?.unobserve(entry.target);}});}, options);// 开始观察所有的元素imageRefs.current.forEach((ref) => {if (ref) {observerRef.current?.observe(ref);}});return () => {// 组件卸载时清理观察者if (observerRef.current) {observerRef.current.disconnect();}};}, []);const imageList = Array.from({ length: 10 }, (_, index) => index + 1);return (<div style={{ height: '300px', overflowY: 'auto' }}>{imageList.map((item, index) => (<divkey={index}ref={(ref) => {imageRefs.current[index] = ref as HTMLDivElement;}}style={{height: '200px',width: '200px',backgroundColor: 'gray',marginBottom: '10px',}}/>))}</div>);
}export default LazyLoadComponent;
示例详述
useRef
用于创建imageRefs
和observerRef
引用,imageRefs
用于存储每个元素的引用,observerRef
用于存储IntersectionObserver
的实例。useEffect
中创建了IntersectionObserver
实例,并设置了观察的选项。在entries
的回调中,当元素进入可视区域时进行相应的操作,这里只是简单地打印了信息。- 在返回的组件结构中,模拟了一个包含多个灰色方块的列表,每个方块都有一个
ref
,用于被观察。
注意,实际应用中,你需要根据具体的需求进行更多的逻辑处理和样式调整,比如实际的图片加载、数据获取等操作。
2、react-lazyload
在 React 项目中,react-lazyload
可以用于长列表加载。
(一)基本原理和适用场景
react-lazyload
的核心原理是监听元素是否进入可视区域,当元素进入可视区域时才触发实际的加载操作。对于长列表加载场景,这一特性非常有用。
在长列表中,可能存在大量的数据项需要展示,一次性加载所有数据项可能会导致性能问题,尤其是在处理图片等资源较大的内容时。使用 react-lazyload
可以延迟加载列表中的元素,只有当用户滚动到相应位置,元素即将进入可视区域时才进行加载,这样可以显著提高初始页面加载速度和整体的用户体验。
(二)使用示例
以下是一个在 React 项目中使用 react-lazyload
处理长列表加载的简单示例:
-
首先,安装
react-lazyload
:npm install react-lazyload
-
然后在代码中使用:
import React from 'react';
import LazyLoad from 'react-lazyload';
import './App.css';const ListItem = ({ index }) => (<div style={{ height: 100, backgroundColor: 'lightblue', marginBottom: 10 }}>列表项 {index}</div>
);const LongList = () => {const listLength = 100;const listItems = [];for (let i = 0; i < listLength; i++) {listItems.push(<ListItem key={i} index={i} />);}return (<div style={{ height: 500, overflowY: 'scroll' }}>{listItems.map((item, index) => (<LazyLoad key={index} once={true}>{item}</LazyLoad>))}</div>);
};export default LongList;
在上述示例中,创建了一个包含 100 个列表项的长列表,通过 react-lazyload
的 LazyLoad
组件包裹每个列表项,实现了懒加载功能。当用户滚动列表时,每个列表项会根据其是否进入可视区域来决定是否进行加载。
(三)性能优势
-
减少初始加载时间:在长列表场景下,不必在页面初始加载时就加载所有的列表项内容,尤其是当列表项包含较大的图片或其他资源时,这可以大大减少初始页面加载时间,让用户更快地看到页面的主要内容。
-
降低内存占用:由于不是一次性加载所有数据,因此可以减少内存的占用,特别是对于移动设备或内存有限的环境,这有助于提高设备的响应速度和整体性能。
-
优化用户体验:通过逐步加载内容,避免了因为大量数据同时加载而导致的页面卡顿或无响应现象,用户可以在滚动过程中平滑地浏览列表内容,提升了用户体验。
(四)注意事项
样式处理:在使用 react-lazyload
时,需要注意列表项的样式设置。特别是当列表项的高度或宽度不确定时,可能会导致懒加载的判断出现偏差。可以通过固定列表项的尺寸或者使用合适的 CSS 布局技巧来解决这个问题。
三、tree-shaking
1、package.json
中的 sideEffects
配置
- 在
package.json
中添加"sideEffects"
字段:
如果你的项目中所有的.css
文件都没有副作用(例如没有在 CSS 中使用:global
或类似会产生全局影响的选择器),可以将"sideEffects"
配置为false
,这将告诉 Webpack 可以更激进地进行 Tree Shaking。
{"name": "your-app","version": "1.0.0","sideEffects": false}
如果项目中有部分文件有副作用,你可以这样配置:
{"name": "your-app","version": "1.0.0","sideEffects": ["*.css","some-module-with-side-effects"]
}
这里列出了有副作用的文件或模块,其他未列出的模块将被更积极地进行 Tree Shaking
。
2、组件按需加载Babel-plugin-import
以下是一个在 React 项目中使用 Babel-plugin-import
的代码示例。
-
首先创建一个简单的 React 项目结构:
my-react-app/ ├── package.json ├── src/ │ ├── App.js │ └── index.js
-
在
package.json
中添加必要的依赖:{"dependencies": {"react": "^18.2.0","react-dom": "^18.2.0"},"devDependencies": {"@babel/core": "^7.22.10","@babel/plugin-proposal-class-properties": "^7.22.3","@babel/plugin-transform-runtime": "^7.22.5","@babel/preset-env": "^7.22.5","@babel/preset-react": "^7.18.6","babel-loader": "^9.1.2"} }
-
创建
.babelrc
文件并配置Babel-plugin-import
:{"presets": ["@babel/preset-react","@babel/preset-env"],"plugins": [["import",{"libraryName": "antd","libraryDirectory": "es","style": "css"}]] }
-
在
src/App.js
中编写示例代码:import React from 'react'; // 使用 Babel-plugin-import 优化引入 antd 的 Button 组件 import { Button } from 'antd';const App = () => {return (<div><Button type="primary">点击我</Button></div>); };export default App;
-
在
src/index.js
中渲染App
组件:import React from 'react'; import ReactDOM from 'react-dom'; import App from './App';ReactDOM.render(<App />, document.getElementById('root'));
-
假设使用 Webpack 进行构建,配置
webpack.config.js
:const path = require('path');module.exports = {entry: './src/index.js',output: {path: path.resolve(__dirname, 'dist'),filename: 'bundle.js'},module: {rules: [{test: /\.(js|jsx)$/,exclude: /node_modules/,use: {loader: 'babel-loader'}}]} };
这样,在项目中通过 Babel-plugin-import
对 antd
的组件引入进行了优化,实际应用中可以根据自己的项目需求和库的使用情况进行相应的调整。
3、使用 Lodash 库的优化
以下是一个简单的代码示例,展示如何在 React 项目中使用 lodash-es
版本并结合 Webpack 的 Tree Shaking 功能:
-
创建一个 React 项目:
npx create-react-app my-lodash-example cd my-lodash-example
-
安装
lodash-es
:npm install lodash-es
-
创建一个示例组件
App.js
:import React from 'react'; import pick from 'lodash-es/pick';const data = {name: 'John',age: 30,city: 'New York' };const filteredData = pick(data, ['name', 'age']);const App = () => {return (<div><p>Name: {filteredData.name}</p><p>Age: {filteredData.age}</p></div>); };export default App;
-
在
package.json
中确保"sideEffects": false
(如果你的项目没有真正的副作用):{"name": "my-lodash-example","version": "0.1.0","private": true,"dependencies": {//..."lodash-es": "^4.17.21","react": "^18.2.0","react-dom": "^18.2.0","react-scripts": "5.0.1"},"sideEffects": false,"scripts": {"start": "react-scripts start","build": "react-scripts build","test": "react-scripts test","eject": "react-scripts eject"} }
-
因为
create-react-app
隐藏了 Webpack 配置,但是在生产构建模式下(npm run build
),它默认会启用 Tree Shaking。
在这个示例中,我们只从 lodash-es
中引入了 pick
函数,并且通过配置 sideEffects
和在生产构建时,Webpack 会进行 Tree Shaking 来去除未使用的代码。
在 create-react-app
项目中,虽然隐藏了 Webpack 配置,但默认在生产构建时已经开启了一些优化措施包括 Tree Shaking,不过你可以通过以下几种方式来进一步优化和确保 Tree Shaking 效果:
4、使用 purgecss
(针对 CSS)
-
安装
purgecss
及其相关依赖:npm install purgecss purgecss-webpack-plugin --save-dev
-
在
webpack.config.js
(虽然create-react-app
隐藏了此文件,但可以通过eject
暴露出来,这是一个不可逆操作,需谨慎考虑)中添加PurgeCSSPlugin
:const PurgeCSSPlugin = require('purgecss-webpack-plugin');module.exports = {//...其他配置plugins: [new PurgeCSSPlugin({paths: glob.sync(`${paths.appSrc}/**/*`, { nodir: true }),}),], };
这将帮助去除未使用的 CSS 代码,与 Tree Shaking 一起优化项目体积。
请注意,在对 create-react-app
的配置进行修改时,尤其是涉及到 eject
操作,要充分了解其影响和风险,并且在修改前最好备份项目代码。
相关文章:
前端宝典之七:React性能优化实战精华篇
本文主要讲解实战项目中React性能优化的方法,主要分为三个大的方面:减少不必要的组件更新、组件优化以及tree-shaking,共11个方法 一、减少不必要组件更新 以下是一些可以避免在 React 提交阶段进行不必要重新渲染的方法: 1、使…...

【Dash】feffery_antd_components 简单入门示例
一、简单了解 feffery_antd_components 简称 fac ,是一个基于 Ant Design 的 Dash 第三方组件,由Feffery 老师开源维护的 Python 网页开发组件库,它具有丰富的页面常用交互组件功能,使开发者可以使用纯Python的方式快速构建现代…...
JAVA学习-练习试用Java实现“路径交叉”
问题: 给定一个整数数组 distance 。从 X-Y 平面上的点 (0,0) 开始,先向北移动 distance[0] 米,然后向西移动 distance[1] 米,向南移动 distance[2] 米,向东移动 distance[3] 米,持续移动。也就是说&#…...
element组件封装
1.上传组件 <!--文件上传组件--> <template><div class"upload-file"><el-uploadref"fileUpload"v-if"props.type default":action"baseURL other.adaptationUrl(props.uploadFileUrl)":before-upload"h…...
Mysql (面试篇)
目录 唯一索引比普通索引快吗 MySQL由哪些部分组成,分别用来做什么 MySQL查询缓存有什么弊端,应该什么情况下使用,8.0版本对查询缓存由上面变更 MyISAM和InnoDB的区别有哪些 MySQL怎么恢复半个月前的数据 MySQL事务的隔离级别ÿ…...

【python】深入探讨python中的抽象类,创建、实现方法以及应用实战
✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,…...
微前端传值
在微前端架构中,不同子应用之间通过 postMessage 进行通信是一种常见的做法。这种方式允许不同源的窗口之间进行安全的信息交换。 下面是如何使用 postMessage 在微前端环境中发送和接收消息的示例。 步骤 1: 发送消息 假设您有一个主应用(host app&a…...

《学会 SpringBoot · 依赖管理机制》
📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗 🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数…...

全网行为管理软件有哪些?5款总有一款适合你的企业!
如今企业越来越依赖互联网进行日常运营和业务发展,网络行为管理变得日益重要。 为了确保网络安全、提高员工工作效率、避免敏感信息外泄等问题,企业往往需要借助全网行为管理软件来监控和管理内部网络的使用情况。 本文将为您介绍五款热门的全网行为管理…...

以简单的例子从头开始建spring boot web多模块项目(二)-mybatis简单集成
继续使用以简单的例子从头开始建spring boot web多模块项目(一)中的项目进行mybatis集成。 1、pom.xml文件中,增加相关的依赖包的引入,分别是mybatis-spring-boot-starter、lombok、mysql-connector-java 如下: <d…...

Golang | Leetcode Golang题解之第354题俄罗斯套娃信封问题
题目: 题解: func maxEnvelopes(envelopes [][]int) int {n : len(envelopes)if n 0 {return 0}sort.Slice(envelopes, func(i, j int) bool {a, b : envelopes[i], envelopes[j]return a[0] < b[0] || a[0] b[0] && a[1] > b[1]})f : …...

jmeter中添加ip欺骗
1、首先在本机电脑中通过配置文件创建添加ip的配置文件,先创建一个txt格式的,直接修改文件名以及后缀为ips.bat 2、编辑该ips.bat文件,在文件中输入如下内容,用于快速给本机添加ip地址,(2,1&…...

WPF篇(19)-TabControl控件+TreeView树控件
TabControl控件 TabControl表示包含多个共享相同的空间在屏幕上的项的控件。它也是继承于Selector基类,所以TabControl也只支持单选操作。另外,TabControl的元素只能是TabItem,这个TabItem继承于HeaderedContentControl类,所以Ta…...

appium下载及安装
下载地址:https://github.com/appium/appium-desktop/releases 双击安装就可以...

XSS项目实战
目录 一、项目来源 二、实战操作 EASY 1 2 3 4 5 6 7 8 一、项目来源 XSS Game - Learning XSS Made Simple! | Created by PwnFunction 二、实战操作 EASY 1 1.Easy -1 2.题目要求及源码 Difficulty is Easy.Pop an alert(1337) on sandbox.pwnfunction.com.No …...

SD-WAN降低网络运维难度的关键技术解析
为什么说SD-WAN(软件定义广域网)大大降低了网络运维的复杂性,主要是因为它的智能路径选择、应用识别和链路质量监测这三个核心技术。这几项在SD-WAN中尤为重要的技术,它们共同作用,提升了整体网络性能,为网…...

【算法基础实验】图论-最小生成树-Prim的即时实现
理论知识 Prim算法是一种用于计算加权无向图的最小生成树(MST, Minimum Spanning Tree)的贪心算法。最小生成树是一个连通的无向图的子图,它包含所有的顶点且总权重最小。Prim算法从一个起始顶点开始,不断将权重最小的边加入生成…...

LLama 3 跨各种 GPU 类型的基准测试
2024 年 4 月 18 日,AI 社区对 Llama 3 70B 的发布表示欢迎,这是一款最先进的大型语言模型 (LLM)。该型号是 Llama 系列的下一代产品,支持广泛的用例。该模型 istelf 在广泛的行业平台上表现良好,并提供了新…...
FreeRTOS 快速入门(五)之信号量
目录 一、信号量的特性1、信号量跟队列的对比2、两种信号量的对比 二、信号量1、二值信号量1.1 二值信号量用于同步1.2 二值信号量用于互斥 2、计数信号量 三、信号量函数1、创建2、删除3、give/take 一、信号量的特性 信号量(Semaphore)是一种实现任务…...
centos 服务器之间实现免密登录
为了在CentOS服务器之间实现免密登录,你需要使用SSH的公钥认证机制 比如两台centos系统的服务器A 和服务器B 首先我们实现从A服务器可以免密登录到服务器B上 首先生成公钥和秘钥: ssh-keygen -t rsa 生成了公钥和秘钥之后: ssh-copy-id r…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...

el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...

《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...

力扣热题100 k个一组反转链表题解
题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...