React之组件渲染性能优化

关键词: shouldComponentUpdate、PureComnent、React.memo、useMemo、useCallback

shouldComponentUpdate 与 PureComnent

shouldComponentUpdatePureComnent 用于类组件。虽然官方推荐使用函数组件,但我们依然需要对类组件的渲染优化策略有所了解,不仅是维护旧的类组件代码需要,很多优化的概念是通用的。

所以,我们先简单了解一下 shouldComponentUpdatePureComnent

先看一个类组件示例

import React from 'react';class Child extends React.Component {render() {console.log('Child rendered');return (<div><h1>Child Count: {this.props.count}</h1></div>);}
}class App extends React.Component {state = {count: 0,otherValue: 'Hello',};increment = () => {this.setState((prevState) => ({ count: prevState.count + 1 }));};changeOtherValue = () => {this.setState({ otherValue: this.state.otherValue === 'Hello' ? 'World' : 'Hello' });};render() {console.log('Parent rendered');return (<div><h1>otherValue: {this.state.otherValue}</h1><Child count={this.state.count} /><button onClick={this.increment}>Increment Count</button><button onClick={this.changeOtherValue}>Change Other Value</button></div>);}
}export default App;

在上面的代码中,Child 组件的 count 属性是 App 的 state 的一部分。点击 APP 组件的 Increment Count,count 会增加,此时 App 组件重新渲染了,Child 组件也重新渲染了:

在这里插入图片描述

点击 APP 组件的 Change Other Value,otherValue 会改变,此时 App 组件重新渲染了,但 Child 组件虽然没有用到 otherValue,但依然重新渲染了:
在这里插入图片描述

这是因为当 Parent 组件(在这个案例中是 App)的 stateprops 发生变化时,React 会默认重新渲染该组件及其所有 Child 组件。

此时就可以用到shouldComponentUpdate 来优化性能,避免不必要的渲染。

shouldComponentUpdate

文档:https://zh-hans.react.dev/reference/react/Component#shouldcomponentupdate

shouldComponentUpdate 是一个生命周期方法,可以用来决定组件是否需要更新。返回 true 会让组件继续更新,而返回 false 则会阻止更新。

使用 shouldComponentUpdate 优化后的 Child 代码如下:

class Child extends React.Component {shouldComponentUpdate(nextProps) {// 仅在 count 属性变化时重新渲染return this.props.count !== nextProps.count;}render() {console.log('Child rendered');return (<div><h1>Child Count: {this.props.count}</h1></div>);}
}

此时,点击 APP 组件的 Change Other ValueotherValue 会改变,但 Child 组件不会重新渲染:

在这里插入图片描述

PureComponent

除了手动实现 shouldComponentUpdate,我们还可以使用 React.PureComponent来自动处理这一逻辑。PureComponent 会对其 props 进行浅比较,如果 props 没有变化,则不会重新渲染。

下面是使用 PureComponent 重写 Counter 组件的示例:

class Child extends React.PureComponent {render() {console.log('Child rendered');return (<div><h1>Child Count: {this.props.count}</h1></div>);}
}

使用 PureComponent 后,Child 组件在 props.count 没有变化时将也不会重新渲染。

需要注意的是,PureComponent 并未实现 shouldComponentUpdate()

React.PureComponent 只进行浅比较,如果 props 或 state 中包含复杂的数据结构(如对象或数组),浅比较可能无法正确判断数据是否发生变化。在这种情况下,可以使用深比较或手动实现 shouldComponentUpdate 来确保组件正确地更新。(但其实我们一般在更新数组时都是返回一个新的数组从而改变引用地址)。

React.memo

文档:https://zh-hans.react.dev/reference/react/memo

其实在官方文档中,shouldComponentUpdate 和 PureComponent 都被列为了过时的 API,官方推荐使用 React.memo 来代替。

React.memo 是一个高阶组件,类似于 PureComponent,但其使用于函数组件。它接受一个函数组件作为参数,并返回一个新的函数组件。新的函数组件会对传入的 props 进行浅比较来决定是否重新渲染组件。

把上面的组件改成函数组件,并在 Child 组件使用 React.memo

import React, { useState } from 'react';// 将 Child 组件定义为函数组件并使用 React.memo
const Child = React.memo(({ count }) => {console.log('Child rendered');return (<div><h1>Child Count: {count}</h1></div>);
});const App = () => {const [count, setCount] = useState(0);const [otherValue, setOtherValue] = useState('Hello');const increment = () => {setCount((prevCount) => prevCount + 1);};const changeOtherValue = () => {setOtherValue((prevValue) => (prevValue === 'Hello' ? 'World' : 'Hello'));};console.log('Parent rendered');return (<div><Child count={count} /><button onClick={increment}>Increment Count</button><button onClick={changeOtherValue}>Change Other Value</button></div>);
};export default App;

在这里插入图片描述

可以看到,使用 React.memo可以和 PureComponent 一样,当 props.count 没有变化时,Child 组件不会重新渲染。

前面说到 React.memo 是一个高阶组件。实际上, React.memo 的源码就是返回一个具有类似于 PureComponent 的行为的组件

需要注意的是,React.memo 也是只对 props 进行浅比较

那么,如果 Child 组件的 props 中包含复杂的数据结构,我们在更新时习惯性地返回一个新的对象或数组,就能避免浅比较的问题。

React.memo 语法

除此之外,React.memo 还可以接受第二个参数,用于自定义比较逻辑。第二个参数是一个函数,接受两个参数:oldPropsnewProps,返回一个布尔值,表示是否需要重新渲染组件。

function MyComponent(props) {/* 使用 props 渲染 */
}
export default React.memo(MyComponent, areEqual);// 自定义比较逻辑
function areEqual(oldProps, newProps) {// 在这里自定义规则// 如果返回true,表示新旧props相等,不渲染 与shouldComponentUpdate相反// 如果返回false,表示新旧props不等,重新渲染
}

useCallback

useCallback 是一个 React Hook,用于优化函数组件的性能。具体的作用简单来说就是缓存函数

文档:https://zh-hans.react.dev/reference/react/useCallback

仅使用 React.memo 时遇到的问题

在实际开发时,在一个组件中会出现很多 Child 组件。我们还是以之前的例子为例,把 countincrement 放到 Child 组件中:

import React, { useState } from 'react';// 将 Child 组件定义为函数组件并使用 React.memo
const Child = React.memo(() => {console.log('Child rendered');const [count, setCount] = useState(0);const increment = () => {setCount((prevCount) => prevCount + 1);};return (<div style={{ border: '1px solid black', width: '300px', padding: '10px' }}><h1>Child Count: {count}</h1><button onClick={increment}>Increment Count</button></div>);
});const App = () => {const [otherValue, setOtherValue] = useState('Hello');const changeOtherValue = () => {setOtherValue((prevValue) => (prevValue === 'Hello' ? 'World' : 'Hello'));};console.log('Parent rendered');return (<div><h1>otherValue: {otherValue}</h1><button onClick={changeOtherValue}>Change Other Value</button><Child /></div>);
};export default App;

分别点击 Increment Count 按钮和 Change Other Value 按钮,可以看到,各自的更新没有互相影响。
在这里插入图片描述

(因为在 Child 使用了 React.memo, 所以 otherValue 的改变不会导致 Child 组件重新渲染。如果不使用 React.memo,点击 Change Other Value 按钮时,Child 组件会重新渲染)

但是,如果 countincrement 在 Parent 组件中定义,那么每次 Parent 组件重新渲染时,都会创建新的 countincrement 函数,导致 Child 组件也重新渲染。

import React, { useState } from 'react';// 将 Child 组件定义为函数组件并使用 React.memo
const Child = React.memo(({ count, increment }) => {console.log('Child rendered');return (<div style={{ border: '1px solid black', width: '300px', padding: '10px' }}><h1>Child Count: {count}</h1><button onClick={increment}>Increment Count</button></div>);
});// Parent 组件: App
const App = () => {const [count, setCount] = useState(0);const [otherValue, setOtherValue] = useState('Hello');const increment = () => {setCount((prevCount) => prevCount + 1);};const changeOtherValue = () => {setOtherValue((prevValue) => (prevValue === 'Hello' ? 'World' : 'Hello'));};console.log('Parent rendered');return (<div><h1>otherValue: {otherValue}</h1><button onClick={changeOtherValue}>Change Other Value</button><Child count={count} increment={increment} /></div>);
};export default App;

点击查看输出
在这里插入图片描述

可以看到,otherValue 变化时,这个输出不太合理, Child 组件没有使用 otherValue 但也重新渲染了。

这是因为每次 Parent 组件重新渲染时,都会创建新的 increment 函数。对于 Child 组件来说传入的 increment 导致 props 不同,所以也会重新渲染。

此时,就可以使用 useCallback 来缓存 increment 函数,避免每次都重新创建。

useCallback 的语法:
const memoizedCallback = useCallback(fn, dependencies);
// fn:回调函数
// dependencies:依赖数组。当依赖数组中的值发生变化时,才会重新生成回调函数

使用 useCallback 把 Parent 组件传入的 increment 函数缓存起来:

const increment = useCallback(() => {setCount((prevCount) => prevCount + 1);
}, []);
// 示例的函数比较简单,并不需要响应任何状态或属性的变化,只需要在组件首次渲染时创建就可以了,所以依赖数组为空数组。

看一下效果:
在这里插入图片描述

可以看到,otherValue 变化时,Child 组件没有重新渲染,达到了我们想要的效果。

在实际应用中,React.memouseCallback 经常结合使用,以减少不必要的组件渲染和函数创建,从而提高性能。

useMemo

说到这里,不得不提 React 提供的另一个 Hook: useMemo。 其用于缓存计算结果,避免在每次渲染时都重新计算。

文档:https://zh-hans.react.dev/reference/react/useMemo

useMemo 的语法:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
// computeExpensiveValue:计算函数
// [a, b]:依赖数组。当依赖数组中的值发生变化时,才会重新计算
使用场景

某些时候,组件中某些值需要根据状态进行一个二次计算(类似于 Vue 中的计算属性),由于组件一旦重新渲染,就会重新执行整个函数,这就导致之前的二次计算也会重新执行一次,从而浪费性能。

例如,我们实现一个购物车时,总价需要根据当前购物车里面的商品内容进行计算,如果每次组件重新渲染时都重新计算总价,就会浪费性能。这时,我们就可以使用 useMemo 来缓存计算结果,避免每次都重新计算。

示例

还是是上面的例子,我们现在要根据 count 的值来计算一个num

import React, { useState } from 'react';function App() {const [count, setCount] = useState(0);const [otherValue, setOtherValue] = useState('Hello');console.log('App 渲染了');function getNum() {console.log('getNum调用了');return count + 100;}const increment = useCallback(() => {setCount((prevCount) => prevCount + 1);}, []);const changeOtherValue = () => {setOtherValue((prevValue) => (prevValue === 'Hello' ? 'World' : 'Hello'));};return (<div><h1>getNum:{getNum()}</h1><h1>otherValue: {otherValue}</h1><div><button onClick={increment}>Increment Count</button><button onClick={changeOtherValue}>Change Other Value</button></div></div>);
}export default App;

运行一下,点击按钮,可以看到控制台输出:
在这里插入图片描述

可以看到,不管是更新 count 还是 otherValuegetNum 都会重新调用。但是,当 otherValue 变化时,其实没必要重新执行 getNum

此时就可以使用 useMemo 来缓存 getNum 的计算结果:

import React, { useState, useMemo } from 'react';function App() {const [count, setCount] = useState(0);const [otherValue, setOtherValue] = useState('Hello');console.log('App 渲染了');const getNum = useMemo(() => {console.log('getNum调用了');return count + 100;}, [count]);// 依赖数组为[count],只有当 count 变化时,才会重新计算 getNumconst increment = useCallback(() => {setCount((prevCount) => prevCount + 1);}, []);const changeOtherValue = () => {setOtherValue((prevValue) => (prevValue === 'Hello' ? 'World' : 'Hello'));};return (<div><h1>getNum:{getNum}</h1><h1>otherValue: {otherValue}</h1><div><button onClick={increment}>Increment Count</button><button onClick={changeOtherValue}>Change Other Value</button></div></div>);
}export default App;

运行,点击按钮,可以看到控制台输出:
在这里插入图片描述

可以看到,当 otherValue 变化时,getNum 没有重新调用,达到了我们想要的效果。

总结

下面对 React.memouseCallbackuseMemo 进行一个简单的对比总结:

特性React.memouseCallbackuseMemo
主要功能缓存组件,防止不必要的渲染缓存回调函数缓存计算结果
使用场景当传入的 props 没有变化时,避免组件重新渲染传递函数到子组件时,避免重新渲染时重新创建该函数避免在每次渲染时,进行不必要的昂贵计算
依赖项根据 props 变化根据依赖数组变化根据依赖数组变化
返回值类型返回新的组件返回记忆化的函数返回记忆化的值

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面经汇总——第一篇

1. int数据类型做了什么优化 Java在处理整数类型时&#xff0c;进行了多种优化&#xff0c;主要体现在编译器层面和JVM层面&#xff0c;目的是提高性能、减少内存开销。 常量池优化 Java中的Integer类有一个缓存机制&#xff0c;对于值在-128到127之间的int数字&#xff0c;Int…

springBoot集成nacos注册中心以及配置中心

一、安装启动nacos 访问&#xff1a;http://127.0.0.1:8848/nacos/index.html#/login 二、工程集成nacos 1、引入依赖 我这里搭建的父子工程哈&#xff0c;在子工程引入 <dependencies><!-- SpringBoot Web --><dependency><groupId>org.sp…

代码审计-Python Flask

1.Jinjia2模版注入 Flask是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug &#xff0c;模板引擎则使用 Jinja2。jinja2是Flask作者开发的一个模板系统&#xff0c;起初是仿django模板的一个模板引擎&#xff0c;为Flask提供模板支持&#xff0c;由于…

MySQL-30.索引-介绍

一.索引 为什么需要索引&#xff1f;当我们没有建立索引时&#xff0c;要在一张数据量极其庞大的表中查询表里的某一个值&#xff0c;会非常的消耗时间。以一个6000000数据量的表为例&#xff0c;查询一条记录的时间耗时约为13s&#xff0c;这是因为要查询符合某个值的数据&am…

RabbitMQ系列学习笔记(八)--发布订阅模式

文章目录 一、发布订阅模式原理二、发布订阅模式实战1、消费者代码2、生产者代码3、查看运行结果 本文参考&#xff1a; 尚硅谷RabbitMQ教程丨快速掌握MQ消息中间件rabbitmq RabbitMQ 详解 Centos7环境安装Erlang、RabbitMQ详细过程(配图) 一、发布订阅模式原理 在开发过程中&…

SpringBoot+MyBatis+MySQL项目基础搭建

一、新建项目 1.1 新建springboot项目 新建项目 选择SpringBoot&#xff0c;填写基本信息&#xff0c;主要是JDK版本和项目构建方式&#xff0c;此处以JDK17和Maven举例。 1.2 引入依赖 选择SpringBoot版本&#xff0c;勾选Lombok&#xff0c;Spring Web&#xff0c;MyBa…

UI自动化测试 —— web端元素获取元素等待实践!

前言 Web UI自动化测试是一种软件测试方法&#xff0c;通过模拟用户行为&#xff0c;自动执行Web界面的各种操作&#xff0c;并验证操作结果是否符合预期&#xff0c;从而提高测试效率和准确性。 目的&#xff1a; 确保Web应用程序的界面在不同环境(如不同浏览器、操作系统)下…

设计模式和软件框架的关系

设计模式和软件框架在软件开发中都有助于解决复杂问题和提高代码质量&#xff0c;但它们在概念和使用上存在一些区别。它们的关系可以通过以下几点理解&#xff1a; 层次与抽象程度 设计模式&#xff08;Design Patterns&#xff09;是一组通用的、可复用的解决方案&#xff0c…

完爆YOLOv10!Transformer+目标检测新算法性能无敌,狠狠拿捏CV顶会!

百度最近又搞了波大的&#xff0c;推出了一种全新的实时端到端目标检测算法RT-DETRv3&#xff0c;性能&耗时完爆YOLOv10。 RT-DETRv3基于Transformer设计&#xff0c;属于代表模型DETR的魔改进化版。这类目标检测模型都有着强大的扩展性与通用性&#xff0c;因为Transform…

MySQL—CRUD—进阶—(二) (ಥ_ಥ)

文本目录&#xff1a; ❄️一、新增&#xff1a; ❄️二、查询&#xff1a; 1、聚合查询&#xff1a; 1&#xff09;、聚合函数&#xff1a; 2&#xff09;、GROUP BY子句&#xff1a; 3&#xff09;、HAVING 子句&#xff1a; 2、联合查询&#xff1a; 1&#xff09;、内连接…

基于FPGA的以太网设计(五)

之前简单介绍并实现了ARP协议&#xff0c;今天简单介绍一下IP协议和ICMP协议。 1.IP协议 IP协议即Internet Protocol&#xff0c;是网络层的协议。 IP协议是TCP/IP协议族的核心协议&#xff0c;其主要包含两个方面&#xff1a; IP头部信息。IP头部信息出现在每个IP数据报中…

第13篇:无线与移动网络安全

目录 引言 13.1 无线网络的安全威胁 13.2 无线局域网的安全协议 13.3 移动通信中的安全机制 13.4 蓝牙和其他无线技术的安全问题 13.5 无线网络安全的最佳实践 13.6 总结 第13篇&#xff1a;无线与移动网络安全 引言 无线和移动网络的发展为我们的生活带来了极大的便利…

边缘计算与联邦学习:探索隐私保护和高效数据处理的结合

个人主页&#xff1a;chian-ocean 文章专栏 边缘计算与联邦学习&#xff1a;探索隐私保护和高效数据处理的结合 1. 引言 随着物联网(IoT)设备的普及&#xff0c;网络边缘产生了大量数据。将这些数据上传至云端进行集中式计算和处理&#xff0c;既有隐私泄露的风险&#xff…

15分钟学Go 实战项目一:命令行工具

实战项目一&#xff1a;命令行工具 1. 引言 命令行工具是开发者常用的工具之一&#xff0c;它可以帮助用户通过命令行界面对程序进行控制和交互。在这节中&#xff0c;我们将创建一个简单的命令行工具&#xff0c;以帮助你理解Go语言的基本语法和如何处理命令行输入。在这个过…

详解安卓和IOS的唤起APP的机制,包括第三方平台的唤起方法比如微信

网页唤起APP是一种常见的跨平台交互方式&#xff0c;它允许用户从网页直接跳转到移动应用程序。 这种技术广泛应用于各种场景&#xff0c;比如让用户在浏览器中点击链接后直接打开某个应用&#xff0c;或者从网页引导用户下载安装应用。实现这一功能主要依赖于URL Scheme、Univ…

ESP32-S3学习笔记:分区表(Partition Table)的二进制分析

一、参考资料 用于研究的官方示例代码&#xff1a;esp-idf-v5.3\examples\storage\partition_api\partition_find参考的官方文档&#xff1a;ESP-IDF编程指南&#xff1a;分区表 二、准备工作 用VS Code打开示例代码&#xff0c;打开示例代码的CSV自定义分区表&#xff0c;如…

大数据实验3: HDFS基础编程

实验3&#xff1a; HDFS基础编程 一、实验目的 HDFS的shell命令使用HDFS的JAVA API使用&#xff1b; 二、实验平台 操作系统&#xff1a;Linux&#xff08;Ubuntu16.04&#xff09;&#xff1b;Hadoop版本&#xff1a;3.3.1&#xff1b;JDK版本&#xff1a;1.8&#xff1b;…

498.对角线遍历

目录 题目解法代码说明&#xff1a;输出&#xff1a; 如何确定起始点&#xff1f;解释一下max(0,d−m1)是什么意思&#xff1f; 如何遍历对角线&#xff1f;.push_back是怎么用的&#xff1f; 题目 给你一个大小为 m x n 的矩阵 mat &#xff0c;请以对角线遍历的顺序&#xf…

Java知识巩固(七)

目录 面向对象 面向对象三大特征 封装 继承 多态 多态 深拷贝和浅拷贝区别了解吗?什么是引用拷贝? 浅拷贝 深拷贝 面向对象 万物皆为对象&#xff0c;也就是描述某个事物解决问题的过程中所发生的事情。 面向对象三大特征 封装 封装是指把一个对象的状态信息&…

目前最新 Reflector V11.1.0.2067版本 .NET 反编译软件

目前最新 Reflector V11.1.0.2067版本 .NET 反编译软件 一、简介二、.NET Reflector的主要功能包括&#xff1a;1. **反编译**: 反编译是将已编译的.NET程序集&#xff08;如.dll或.exe文件&#xff09;转换回可读的源代码。这使得开发者可以查看和学习第三方库的实现细节&…