前端框架 react 性能优化
目录
一、不使用任何性能优化API进行优化
二、通过性能优化API优化
1、React.memo
2、useCallback
3、useMemo
4、PureComponent
三、总结
总览:react的优化核心思想就是让react跳过重新渲染那个些没有改变的Component,而只重新渲染发生变化的Component。
所以变化都是围绕这三种变量展开的:props、state、context 。
一、不使用任何性能优化API进行优化
核心思想:将变和不变的部分分离开。
案例一:没有变化的组件被多次渲染了
function App() {const [num, updateNum] = useState(0)return (<div className="App"><input type="text" onChange={(e) => updateNum(+e.target.value)}/><p>num is {num}</p><ExpensiveCpn/></div>);
}const ExpensiveCpn = () => {let now = performance.now()while (performance.now() - now < 100) {}console.log("组件耗时 render")return <div>耗时组件</div>
}
运行结果:可以看到,在上面代码的情况下,高耗能组件虽然没有被修改,但每次修改state里的数据还是都会重新渲染改组件。
下面对其进行优化,即变的部分组件抽离:
function App() {return (<div className="App"><Input/><ExpensiveCpn/></div>);
}const Input = () => {const [num, updateNum] = useState(0)return (<><input type="text" onChange={(e) => updateNum(+e.target.value)}/><p>num is {num}</p></>)
}const ExpensiveCpn = () => {let now = performance.now()while (performance.now() - now < 100) {}console.log("组件耗时 render")return <div>耗时组件</div>
}
页面效果:我们将页面变的部分和不变的部分进行抽离后实现效果:变的组件发生变化时,不会影响我们的其他组件了。
如果你的代码结果写的好,你几乎可以不用性能优化API。
案例二:与案例一类似,不同在于这里父组件也用到了state里的变量。
function App() {const [num, updateNum] = useState(0)return (<div className="App" title={num + ""}><input type="text" onChange={(e) => updateNum(+e.target.value)}/><p>num is {num}</p><ExpensiveCpn/></div>);
}const ExpensiveCpn = () => {let now = performance.now()while (performance.now() - now < 100) {}console.log("组件耗时 render")return <div>耗时组件</div>
}
这段代码和案例一没有优化前的效果一样。
下面我们将对其进行优化:
function App() {return (<InputWrapper><ExpensiveCpn/></InputWrapper>);
}function InputWrapper({children}) {const [num, updateNum] = useState(0)return (<div className="App" title={num + ""}><input type="text" onChange={(e) => updateNum(+e.target.value)}/><p>num is {num}</p>{children}</div>)
}const ExpensiveCpn = () => {let now = performance.now()while (performance.now() - now < 100) {}console.log("组件耗时 render")return <div>耗时组件</div>
}
页面效果:可以看到通过{children}插槽可以实现父元素也有变量时的优化。
从以上两个案例可以得出以下结论:
当父组件满足性能优化条件时,子孙组件可能命中性能优化。
本质是:将变的部分和不变的部分分离。
二、通过性能优化API优化
为什么需要性能优化API:
当子孙结点的父结点未命中性能优化时,父结点的分支也将不会命中性能优化。
1、React.memo
官方文档:memo – React 中文文档
React.memo是一个高阶组件,它接收另一个组件作为参数,会返回一个包装过的新组件,包装后的新组件就会具有缓存作用。
包装后,只有组件的props发生变化,才会触发组件的重新渲染,否则总是返回缓存中的结果。
案例一:
function App() {console.log("App发生渲染")const [count, setCount] = useState(1)return (<><div className="App"><Son/><p>{count}</p><button onClick={() => setCount(count => ++count)}>点击加1</button></div></>);
}const Son = () => {console.log("Son发生渲染")return <div><p>son</p><GrandSon/></div>
}const GrandSon = () => {console.log("GrandSon发生渲染")return <div><p>GrandSon</p></div>
}
实现效果:每次修改App里的数据,子孙组件都发生了改变。
案例二:
对案例一进行优化:
function App() {console.log("App发生渲染")const [count, setCount] = useState(1)return (<><div className="App"><Son/><p>{count}</p><button onClick={() => setCount(count => ++count)}>点击加1</button></div></>);
}const Son = React.memo(() => {console.log("Son发生渲染")return <div><p>son</p><GrandSon/></div>
})const GrandSon = () => {console.log("GrandSon发生渲染")return <div><p>GrandSon</p></div>
}
实现效果:可以看到,当Son组件被memo包裹时,Son一系列数下的组件都被缓存优化到了。每次修改父组件,子孙组件都未重新渲染。
案例三:
在App组件中,像子组件Son传入修改参数的setCount函数。
function App() {console.log("App发生渲染")const [count, setCount] = useState(1)const addOne = () => {setCount(count => ++count)}return (<><div className="App"><Son addOne={addOne}/><p>{count}</p><button onClick={addOne}>点击加1</button></div></>);
}const Son = React.memo(({addOne}) => {console.log("Son发生渲染")return <div><p>son</p><button onClick={addOne}>Son点击加1</button><GrandSon/></div>
})const GrandSon = () => {console.log("GrandSon发生渲染")return <div><p>GrandSon</p></div>
}
实现效果:当Son组件中调用了父组件中的函数时,你会发现React.memo失效了,此时,子孙组件仍然会被重新渲染。因为App组件发生改变重新渲染,addOne函数也会重新定义,此时传入Son组件里的函数就相当于更新了,会使得Son进行重新渲染。但问题是Son里的内容并没有改变,有什么方法可以解决这种情况的问题呢?这就要用到下面介绍的useCallback钩子了。
2、useCallback
useCallback是一个钩子函数,用来创建React中的回调函数。创建的回调函数不会总在组件重新渲染时重新创建。简单来说,就是对回调函数做了一层缓存。
const cachedFn = useCallback(fn, dependencies)
useCallback的第一个参数是一个回调函数,第二个参数是依赖数组(当依赖数组中的变量发生变化时,回调函数才会重新创建;如果不指定依赖数组,回调函数每次都会重新创建,失去意义)。
注:如果使用时,不传第二个参数,函数仍然会在每次渲染时重新创建,和没使用没什么区别。
案例四:
function App() {console.log("App发生渲染")const [count, setCount] = useState(1)const addOne = useCallback(() => {setCount(count => ++count)},[]) // 这里的函数只会在组件初始化时创建,更新时不会再次创建return (<><div className="App"><Son addOne={addOne}/><p>{count}</p><button onClick={addOne}>点击加1</button></div></>);
}const Son = React.memo(({addOne}) => {console.log("Son发生渲染")return <div><p>son</p><button onClick={addOne}>Son点击加1</button><GrandSon/></div>
})const GrandSon = () => {console.log("GrandSon发生渲染")return <div><p>GrandSon</p></div>
}
实现效果:React.memo案例三中的问题解决了
案例五:
function App() {console.log("App发生渲染")const [count, setCount] = useState(1)const [num, setNum] = useState(1)const addOne = useCallback(() => {setCount(count => count + num)setNum(num => num + 1)}, [num])return (<><div className="App"><Son addOne={addOne}/><p>{count}</p><button onClick={addOne}>点击加1</button></div></>);
}const Son = React.memo(({addOne}) => {console.log("Son发生渲染")return <div><p>son</p><button onClick={addOne}>Son点击加1</button><GrandSon/></div>
})const GrandSon = () => {console.log("GrandSon发生渲染")return <div><p>GrandSon</p></div>
}
实现效果:在依赖数组中数组callback函数中使用到的变化,每当数组中的变量发生改变时,回调函数都会重新定义,执行时机和useEffect依赖数组里是类似的。但案例中这种情况已经失去了优化的初衷。所以,案例五中的情况,尽量不要用useCallback了,使用和不使用效果都一样,还多了一层缓存的耗时操作。
3、useMemo
相当于Vue里的Computed
与useCallback十分相似,useCallback是用来缓存函数对象,useMemo是用来缓存函数的执行结果。
不使用useMemo的执行效果:
const sum = (a, b) => {console.log("求和执行了")return a + b
}function App() {console.log(sum(1,2))console.log(sum(1,2))return (<><div></div></>);
}
效果:每次调用都会重新执行。
使用了useMemo:
const sum = (a, b) => {console.log("求和执行了")return a + b
}function App() {const result = React.useMemo(() => {return sum(1, 2)})console.log(result)console.log(result)return (<><div></div></>);
}
效果:函数只执行了一次,对于开销很大的函数,使用useMemo可以很好地改善性能。
当useMemo里传入变量时:
const sum = (a, b) => {console.log("求和执行了")return a + b
}function App() {const [count, setCount] = useState(1)let b = 2// 每次组件渲染时,都会执行// useMemo用于缓存函数的执行结果const result = React.useMemo(() => {return sum(count, b)}, [])useEffect(() => {console.log("count:", count)console.log(result)console.log(result)}, [count])return (<><div><button onClick={() => setCount(prev => prev + 1)}>a加1</button></div></>);
}
打印:可以看到每次执行都是第一次的结果,变量改变也没有重新缓存result。
解决上面问题的方法:
const result = React.useMemo(() => {
return sum(count, b)
}, [count]) // 第二个参数数组中传入对应变量,当变量发生变化时,会重新调用useMemo进行结果缓存更新。
对于上述情况,如果修改过于频繁,就基本使用不到缓存效果,这种情况,不推荐使用useMemo。
另外,useMemo也可以像React.memo一样返回组件缓存:
function App() {const el = useMemo(() => {return <div><p>hello</p></div>}, [])return (<><div>{el}</div></>);
}
4、PureComponent
React.memo的对应。
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。
三、总结
性能优化三部曲:
1、寻找项目中性能损耗严重的子树;
2、在子树的根节点使用性能优化API;
3、子树中运用变与不变分离原则。
总结到此,相信你已经掌握了性能优化的精髓。
相关文章:

前端框架 react 性能优化
目录 一、不使用任何性能优化API进行优化 二、通过性能优化API优化 1、React.memo 2、useCallback 3、useMemo 4、PureComponent 三、总结 总览:react的优化核心思想就是让react跳过重新渲染那个些没有改变的Component,而只重新渲染发生变化的C…...

RK3568平台开发系列讲解(Input子系统篇)输入子系统介绍
🚀返回专栏总目录 文章目录 一、什么是输入子系统?二、输入设备和节点的关系沉淀、分享、成长,让自己和他人都能有所收获!😄 一、什么是输入子系统? 在 Linux 中,input 子系统是专门为处理输入类设备而设计的一个子系统或框架。它提供 了一套通用的接口和机制,用于驱…...

准备阶段 Profiler性能分析工具的使用(一)
Unity 性能分析器 (Unity Profiler) 性能分析器记录应用程序性能的多个方面并显示相关信息。使用此信息可以做出有关应用程序中可能需要优化的事项的明智决策,并确认所做的优化是否产生预期结果。 默认情况下,性能分析器记录并保留游戏的最后 300 帧&a…...

go-rod vs Selenium:自动化测试工具的比较与选择
自动化测试是软件开发过程中的关键环节,它能够帮助我们发现缺陷、验证功能并提高软件质量。随着Web技术的快速发展,市场上出现了多种自动化测试工具,其中Selenium和go-rod是两个备受关注的选择。本文将从多个维度对这两个工具进行比较&#x…...

探索免费的Figma中文版:开启高效设计之旅
在当今数字化设计的浪潮中,Figma以其强大的云端协作功能和出色的设计能力,成为了众多设计师的心头好。而对于国内的设计师来说,能够免费使用Figma中文版更是一大福音,下面就来一起探索一下吧。 一、Figma中文版的获取途径 虽然F…...

功能齐全,支持协作 | Docker部署一款支持多人共享的私密浏览器『n.eko』
功能齐全,支持协作 | Docker部署一款支持多人共享的私密浏览器『n.eko』 哈喽小伙伴们好,我是Stark-C~ 玩NAS的朋友基本都会在本地部署一款浏览器用来远程访问内网的网络设备,或者偶尔拿来浏览一些私密网站都是很方便的。 今天为大家分享的…...

部署实战(二)--修改jar中的文件并重新打包成jar文件
一.jar文件 JAR 文件就是 Java Archive ( Java 档案文件),它是 Java 的一种文档格式JAR 文件与 ZIP 文件唯一的区别就是在 JAR 文件的内容中,多出了一个META-INF/MANIFEST.MF 文件META-INF/MANIFEST.MF 文件在生成 JAR 文件的时候…...

Ubuntu24.04——软件包系统已损坏
如果你在使用 Ubuntu 时遇到“软件包系统已损坏”的问题,可以尝试以下步骤来修复它: 更新软件包列表: 打开终端,运行以下命令以更新软件包列表: sudo apt update修复损坏的软件包: 运行以下命令来修复损坏的…...

2024年华为OD机试真题-空栈压数-C++-OD统一考试(E卷)
最新华为OD机试考点合集:华为OD机试2024年真题题库(E卷+D卷+C卷)_华为od机试题库-CSDN博客 每一题都含有详细的解题思路和代码注释,精编c++、JAVA、Python三种语言解法。帮助每一位考生轻松、高效刷题。订阅后永久可看,发现新题及时跟新。 题目描述: 向一个空栈压入…...

嵌入式Linux基于IMX6ULL tslib学习总结
目录 1. tslib开源库介绍1.1 tslib主要功能1.2 架构 2. tslib代码简单分析2.1 ts_print_mt.c分析代码2.2 ts_setup代码分析2.3 ts_open代码分析2.4 ts_config代码分析2.5 ts_read_mt代码分析2.6 tslib中4个模块的含义 3. 使用tslib库打印触摸屏2点之间的距离 基于韦东山IMX6ULL…...

go中的参数传递是值传递还是引用传递?
在Go语言中,参数传递机制是一个重要的概念,它决定了函数内部对参数的修改是否会影响到原始数据。关于Go中的参数传递是值传递还是引用传递的问题,可以从以下几个方面进行解答。 一、值传递与引用传递的定义 值传递:在值传递中&a…...

记录一种在内核空间向用户空间通知中断的方法
记录一种在内核空间向用户空间通知中断的方法 0.前言1.代码实现1)内核设备驱动实现2)消息通知实现3)测试程序 2.解析 参考文章:Linux驱动实践:中断处理函数如何【发送信号】给应用层? 0.前言 最近在项目中遇到一个需求,需要将一个…...

.NetCore 过滤器和拦截器 的区别
Asp.NET Core 中的过滤器(Filter)和拦截器(Interceptor)是两个不同的概念,但它们在某些方面有相似之处,也有明显的区别。 🔑过滤器(Filter) 过滤器是Asp.NET Core中用于…...

【笔记】自动驾驶预测与决策规划_Part7_数据驱动的预测方法
文章目录 0. 前言1. 多模态传感器的编码方式1.1 栅格化表示1.2 向量化表示 Vectornet1.3 基于点云或者多模态输入的预测1.4 基于Transformer的方法 2. 网络输出的表达形式2.1 多模态轨迹回归2.2 轨迹分类2.3 轨迹回归轨迹分类2.4 目标点预测 3.场景级别的预测和决策3.1 论文&am…...

React渲染相关内容——渲染流程API、Fragment、渲染相关底层API
React渲染过程依次遇到的函数 在React的渲染流程中,从组件的创建到其UI最终呈现到屏幕上,会经历一系列的生命周期方法和函数。这些方法和函数对于类组件(Class Components)和函数组件(Function Components)…...

Python中dict支持多个key的方法
在Python中,字典(dict)是一种非常强大的数据结构,它允许我们通过键(key)来存储和检索值(value)。有时候,我们可能想要根据多个键来检索或操作字典中的数据。虽然Python的…...

丹摩 | 基于PyTorch的CIFAR-10图像分类实现
从创建实例开始的新项目流程 第一步:创建实例 登录 DAMODEL 平台。创建一个 GPU 实例: GPU 配置:选择 NVIDIA H800 或其他可用高性能 GPU。 系统配置:推荐使用 Ubuntu 20.04,内存 16GB,硬盘 50GB。 启…...

C#变量和函数如何和unity组件绑定
1.Button On_click (1)GameObject通过Add component添加上Script (2)Button选GameObject组件而不是直接选Script,直接选Script出现不了Script中的函数 2.RawImage 上面是错的 3.Text 上面是错的,应该是直接在GameObject里面填上对应的值 总结: …...

AI模型---安装cuda与cuDNN
1.安装cuda 先打开cmd 输入nvidia-smi 查看显卡支持cuda对应的版本: 然后去英伟达官网下载cuda(外网多刷几次) https://developer.nvidia.com/cuda-toolkit-archive 注意对应版本 安装过程中如果显示如下图: 请安装visual Stu…...

【大数据学习 | Spark-Core】Spark提交及运行流程
spark的集群运行结构 我们要选择第一种使用方式 命令组成结构 spark-submit [选项] jar包 参数 standalone集群能够使用的选项。 --master MASTER_URL #集群地址 --class class_name #jar包中的类 --executor-memory MEM #executor的内存 --executor-cores NUM # executor的…...

内网渗透横向移动1
1.信息收集 (1)判断域控 shell net time /domain shell ping OWA2010CN-God.god.org (2)主机探测 浏览探测->网络探测 主机列表显示: (3)域用户收集: shell net user /domain…...

现代密码学
概论 计算机安全的最核心三个关键目标(指标)/为:保密性 Confidentiality、完整性 Integrity、可用性 Availability ,三者称为 CIA三元组 数据保密性:确保隐私或是秘密信息不向非授权者泄漏,也不被非授权者使…...

Pod 动态分配存储空间实现持久化存储
配置 Pod 以使用 PersistentVolume 作为存储 关于持久卷的介绍,可以看官方文档 https://kubernetes.io/zh-cn/docs/concepts/storage/persistent-volumes/ 持久卷根据存储位置,可以使用本地存储和云存储,如果有云服务平台,…...

Jackson、Gson、FastJSON三款JSON利器比拼
在Java领域,有多种JSON工具包,比如Jackson、Gson、FastJSON,每家都各有所长,下面我们从性能、特性、生态、易用 性等几个方面来展开下: 一、Jackson 性能 Jackson是一款高性能的JSON处理库。它在序列化和反序列化操作…...

php:nginx如何配置WebSocket代理?
在nginx配置中加入以下配置即可: server {listen 80;server_name test.com;# 配置 WebSocket 代理location /ws {proxy_pass http://127.0.0.1:8083;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade&qu…...

3349、检测相邻递增子数组 Ⅰ
3349、[简单] 检测相邻递增子数组 Ⅰ 1、题目描述 给你一个由 n 个整数组成的数组 nums 和一个整数 k,请你确定是否存在 两个 相邻 且长度为 k 的 严格递增 子数组。具体来说,需要检查是否存在从下标 a 和 b (a < b) 开始的 两个 子数组,…...

C++笔记之函数入参传递std::unique_ptr 时使用 std::move的场景
C++笔记之函数入参传递std::unique_ptr 时使用 std::move的场景 code review! 参考笔记 C++笔记之unique_ptr转移堆内空间的所有权 文章目录 C++笔记之函数入参传递std::unique_ptr 时使用 std::move的场景一.使用 std::unique_ptr 作为函数参数时的主要场景二.一个完整示例一…...

怎么只提取视频中的声音?从视频中提取纯音频技巧
在数字媒体的广泛应用中,提取视频中的声音已成为一项常见且重要的操作。无论是为了学习、娱乐、创作还是法律用途,提取声音都能为我们带来诸多便利。怎么只提取视频中的声音?本文将详细介绍提取声音的原因、工具、方法以及注意事项。 一、为什…...

数仓工具—Hive语法之窗口函数中的 case when
窗口函数中的 case when 今天我们看一下窗口函数和case when 的各种花活,最近的需求各种窗口,一个需求中十几个窗口,加上各种条件边界,所以写了大量的窗口函数和case when的组合,今天我们来看一下。 我们的数据如下 %spark.pyspark df2 = spark.createDataFrame([(&quo…...

基于微信小程序的酒店客房管理系统+LW示例参考
1.项目介绍 系统角色:管理员、员工、普通用户功能模块:员工管理、用户管理、客房管理、预订管理、商品管理、评价管理、续订管理、订单管理等技术选型:SSM,vue,uniapp等测试环境:idea2024,jdk1…...