React/RN组件避免重复渲染的一些技巧

组件基础

回顾下React中创建组件的几种方式:

  • ES5的React.createClass方式
  • ES6的React.Component方式
  • 无状态的函数组件方式
  • 带hooks的函数组件方式

        这里我们只讨论Component和函数组件。我们知道Component是否发生重渲染是由shouldComponentUpdate决定的,默认情况下返true。自定义的Component会根据自身state和props是否发生变化,来决定自身是否需要重新渲染,这个过程往往需要进行深度比较的。而相对应的,PureComponent会通过shadowEqual对state和props进行浅比较,来决定是否需要重新渲染。一般而言,如果state和props没有发生变化,组件本身是不需要重新渲染的。

        Component组件的渲染过程是组件调用自身的render函数来生成虚拟DOM,然后跟上次的进行比较,如果发生变化,就更新对应的平台DOM。React平台以及第三方库提供的组件一般都是基于Component的,因此只要确保props不变,就不会重新渲染。 

        而对于函数组件,只要这个函数被调用,虚拟DOM就会重新生成。从这个角度来看,函数是否执行,跟传给它什么参数是无关的。只是取决于它所在父Component组件是否调用了render函数,或者父函数组件是否被调用了。

        useState这个hooks为函数组件提供了状态保持和触发渲染的功能。我们知道作为纯函数本身它内部是无法持久存储一个值的,每次函数执行完毕,里面创建的所有变量都会被清除。所以useState本质是把状态保存到了函数外部,这样每次执行函数,既能修改保存它的值,也能获取上次执行后的值。

一个React例子

        接下去看一个例子,我们可以把代码拷贝到安装 – React 中文文档 的"尝试 React"中:

import {useState} from 'react'const Parent = () => {const [count, setCount] = useState(0);const [son1Count, setSon1Count] = useState(0);const [son2Count, setSon2Count] = useState(0);return (<div>{console.log("Parent render")}<button onClick={() => setCount(v => v + 1)}>Parent + 1</button><button onClick={() => setSon1Count(v => v + 1)}>Son1 + 1</button><button onClick={() => setSon2Count(v => v + 1)}>Son2 + 1</button><h3>Parent: {count}</h3><Son1 son1Count={son1Count} /><Son2 son2Count={son2Count} /></div>);
};
const Son1 = (props) => {return (<div>{console.log("Son1 render")}Son1: {props.son1Count}</div>);
};
const Son2 = (props) => {return (<div>{console.log("Son2 render")}Son2: {props.son2Count}</div>);
};export default function App() {return <Parent />
}

按照我们前面的描述,点击parent+1按钮会导致Parent组件重新执行,因此Son1和Son2尽管props没有发生变化,但由于它们是函数组件,它们依然会被执行。

使用memo

memo 允许你的组件在 props 没有改变的情况下跳过重新渲染。我们可以用它来改造上面的Son1和Son2:

const Son1 = memo((props) => {return (<div>{console.log("Son1 render")}Son1: {props.son1Count}</div>);
});
const Son2 = memo((props) => {return (<div>{console.log("Son2 render")}Son2: {props.son2Count}</div>);

现在我们再点击parent+1,就会发现Son1和Son2没有重新执行了。

props中包含函数

更新我们的例子,向memo后的Son组件props传递一个函数:

import {useState ,memo} from 'react'
const Parent = () => {const [count, setCount] = useState(0);const [sonCount, setSonCount] = useState(0);const allPlus = () => {setCount(v => v + 1);setSonCount(v => v + 1);};return (<div>{console.log("Parent render")}<button onClick={() => setCount(v => v + 1)}>Parent + 1</button><h3>Parent: {count}</h3><Son allPlus={allPlus} sonCount={sonCount} /></div>);
};
const Son = memo((props) => {return (<div>{console.log("Son render")}<p>Son: {props.sonCount}</p><button onClick={props.allPlus}>All + 1</button></div>);
});export default function App() {return <Parent />
}

尽管我们使用了memo,但当我们点击parent+1按钮时,Son也发生了渲染。问题的根源是Parent组件每次执行时,里面的allPlus函数会重新创建,导致Son的props发生了变化。默认情况下,React 将使用 Object.is 比较每个 prop。

memo的arePropsEqual

一个解决办法是我们实现memo的第二个参数arePropsEqual,以取代默认的实现:

const Son = memo((props) => {return (<div>{console.log("Son render")}<p>Son: {props.sonCount}</p><button onClick={props.allPlus}>All + 1</button></div>);
},
(prevProps, nextProps) => prevProps.sonCount === nextProps.sonCount);

再点击parent+1按钮,我们发现Son不再重新渲染了。

useCallback

另一个解决办法是保持函数的不变性,在React中,我们通常使用useCallback在多次渲染中缓存函数。它的作用看起来和useRef类似,但它还提供了第二个参数dependencies,用来在依赖发生改变时,更新以及返回这一次渲染传入的函数。将代码更新如下:

const allPlus = useCallback(() => {setCount(count + 1);setSonCount(sonCount + 1);
}, []);

这时候点击parent+1按钮也不会引起Son组件重新渲染。

useMemo

保持函数的不变性,我们也可以使用useMemo。useMemo缓存的是函数执行的结果。因为我们要缓存一个函数,因此我们要使用一个返回这个函数的函数作为useMemo的参数,修改代码如下:

const allPlus = useMemo(() => () => { // 注意这里不同于 useCallbacksetCount((v) => v + 1);setSonCount((v) => v + 1);},[]);

memo和useMemo的区别

对于一个函数组件,useMemo可以缓存它的执行结果,而不是函数组件本身。而memo 是高阶组件,它会比较当前组件的 propsstate 是否发生了变化,如果都没有变化,就不会重新渲染该组件,而是直接使用之前的结果。但是memo组件本身每次渲染时都会重新创建。因此,当我们需要保证组件不被重复渲染时,使用memo。当需要保证组件prop的不变性时,使用useMemo。

一个React Native例子

假设我们有一个FlatList,并且支持下拉刷新,因此给它设置了refreshControl。并且在滚动的时候控制一个”返回顶部“按钮的显示与隐藏。

const App = () => {const [refreshing, setRefreshing] = React.useState(false);const flatListRef = useRef<FlatList>(null)const [showTop, setShowTop] = useState(isShowTop)const onRefresh = React.useCallback(() => {setRefreshing(true);wait(2000).then(() => setRefreshing(false));}, []);const handleScrollToTop = () => {flatListRef.current?.scrollToOffset({ animated: true, offset: 0 })}const onScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {const scrollY = e.nativeEvent.contentOffset.yif (scrollY > 0) {setShowTop(true)} else {setShowTop(false)}}, [])return (<SafeAreaView style={styles.container}><FlatListref={flatListRef}data={DATA}onScroll={onScroll}renderItem={({item}) => <Item title={item.title} />}keyExtractor={item => item.id}refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}/>{showTop &&<Button onPress={handleScrollToTop} title="Top"></Button>}</SafeAreaView>);
};

当调用setShowTop时,APP组件会发生重新渲染。这里的onScroll、renderItem、refreshControl都会重新创建,导致FlatList重新渲染。使用前面我们提到的技巧(当然renderItem可以移动到App函数外面去),我们可以缓存这些prop来避免发生变化。对于refreshControl我们可以使用useMemo,例如:

const refreshControl = useMemo(() => <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />, [refreshing])

当然我们常见的代码,大多数使用我们一开始提供的版本,因为大多数情况下,多余的渲染并不会引起显著的性能问题。但是理解并发现多余的渲染,有助于我们在必要时进行相应的优化。

 

参考

https://www.cnblogs.com/hymenhan/p/16325708.html

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

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

相关文章

【每日一题】2024年3月汇编(上)

3.1【2369】检查数组是否存在有效划分 2369. 检查数组是否存在有效划分https://leetcode.cn/problems/check-if-there-is-a-valid-partition-for-the-array/ 1.这样的判断可以用动态规划来解决&#xff0c;用一个长度为(n1) 的数组来记录 是否存在有效划分&#xff0c;dp[i]…

乘法-逆矩阵

文章目录 1. 矩阵相乘-4种方式1.1 CAB1.2 AX 列组合1.3 XB 行组合 2. A,AB, A T A^T AT的逆3. 高斯消元法求 A − 1 A^{-1} A−1 1. 矩阵相乘-4种方式 1.1 CAB 假设我们要求得矩阵CAB &#xff0c;可以用如下公式表示 c i j ∑ k 1 N a i k b k j (1) c_{ij}\sum_{k1}^Na_…

Bean的作用域、Bean的自动装配、注解自动装配 (Spring学习笔记五)

1、Bean 的作用域 官网上显示有六种 1、Bean的作用域默认的是singleton&#xff08;单例模式的实现&#xff09; 也可以显示的设置&#xff08;单例模式的实现&#xff09; <!--用scope可以设置Bean的作用域--><bean id"user2" class"com.li.pojo.Us…

如何实现分词

分词&#xff08;Tokenization&#xff09;是自然语言处理&#xff08;NLP&#xff09;中的一个基本步骤&#xff0c;特别是在构建搜索引擎时&#xff0c;它将文本拆分成单词、短语或其他有意义的元素&#xff08;称为“词素”或“tokens”&#xff09;。对于中文文本来说&…

A*(AStar)算法总结

简介 A* 算法&#xff08;念做&#xff1a;A Star&#xff09;是一种常用的路径查找和图形遍历算法&#xff0c;具有较好的性能和准确度。让我为您简要介绍一下 A* 算法的原理和实现。 广度优先搜索&#xff1a; 广度优先搜索以广度作为优先级进行搜索。从起点开始&#xff0…

NCV8705MTADJTCG稳压器芯片中文资料规格书PDF数据手册引脚图图片价格功能

产品概述&#xff1a; NCV8705 是一款低噪音、低功耗和低泄漏线性电压稳压器。该器件具有卓越的噪音和 PSRR 规格&#xff0c;适用于使用视频接收器、成像传感器、音频处理器或需要外部洁净电源的任何部件的产品。NCV8705 使用创新的自适应接地电流电路 可确保轻负载调节下的超…

IDEA SpringBoot + Gradle无法运行测试问题

解决 i. 查看 build.gradle 中是否配置了 tasks.named(‘test’) { useJUnitPlatform() } ii. 打开IDEA 设置 &#xff08;Mac用户 Com &#xff0c; 可快速打开 / Win用户 Ctrl Alt s&#xff09; 检索Gradle 后&#xff0c;将Run tests using 选项 变更成 intelliJ IDEA…

http请求方法15种,附图可以下载保存备查。

一、http请求组成和流程 HTTP请求是客户端&#xff08;如浏览器&#xff09;向服务器发送的请求&#xff0c;以获取特定资源或执行特定操作。HTTP请求由以下几个部分组成&#xff1a; 请求行&#xff1a;包含请求方法、请求的URL和HTTP协议版本。常见的请求方法有GET、POST、P…

Rust 的 HashMap

在 Rust 中&#xff0c;HashMap 是一个从键&#xff08;key&#xff09;映射到值&#xff08;value&#xff09;的数据结构。它允许你以 O(1) 的平均时间复杂度存储、检索和删除键值对。HashMap 实现了 std::collections::HashMap 结构体&#xff0c;通常通过 use std::collect…

C#--StreamWriter和StreamReader对象及常用函数

目录 StreamReaderReadLine()Split&#xff08;&#xff09; StreamWriterWriteLine&#xff08;&#xff09;Flush()Close() StreamReader StreamReader 是 C# 中用于从流&#xff08;如文件、内存流等&#xff09;中读取文本数据的类。它提供了多种方法来读取不同类型的数据…

关于Canvas绘图和SVG绘图绘图的区别

当谈到Canvas绘图和SVG绘图时&#xff0c;它们是两种不同的绘图技术&#xff0c;各自具有不同的特点和应用场景。 区别&#xff1a; Canvas绘图&#xff1a;Canvas是HTML5中的一个元素&#xff0c;它提供了一个可以通过JavaScript进行绘图的区域。Canvas绘图是基于像素的&…

实景户外剧本杀小程序开发搭建

实景户外剧本杀小程序开发搭建需要以下步骤&#xff1a; 1. 确定需求和设计&#xff1a;首先需要明确实景户外剧本杀小程序的需求&#xff0c;包括场景、剧本、角色、玩法等方面的需求&#xff0c;并根据需求设计小程序的界面和功能。 2. 选择开发技术&#xff1a;根据需求选…

数据结构的概念大合集01(含数据结构的基本定义,算法及其描述)

概念大合集01 1、数据结构基础的定义2、数据结构2.1 数据元素之间关系的集合2.2数据结构的三要素2.2.1数据的逻辑结构2.2.2数据的存储&#xff08;物理&#xff09;结构2.2.3数据的运算 3、数据类型4、抽象数据类型类型&#xff08;ADT&#xff09;5、算法及其描述5.1算法的5个…

Qt文件以及文件夹相关类(QDir、QFile、QFileInfo)的使用

关于Qt相关文件读写操作以及文件夹的一些知识&#xff0c;之前也写过一些博客&#xff1a; Qt关于路径的处理&#xff08;绝对路径、相对路径、路径拼接、工作目录、运行目录&#xff09;_qt 相对路径-CSDN博客 C/Qt 读写文件_qt c 读取文本文件-CSDN博客 C/Qt读写ini文件_…

【C++】C++面向对象练习题

利用多态机制完成以下题目 现需要一个理财程序&#xff0c;其中包含四个类&#xff0c;分别为投资&#xff08;Investment&#xff09;、储蓄&#xff08;Saving)、基金&#xff08;Fund&#xff09;和理财人&#xff08;Person&#xff09;&#xff0c;储蓄和基金为两种具体投…

Docker学习之数据管理(超详解析)

Docker存储资源类型&#xff1a; 用户在使用 Docker 的过程中&#xff0c;势必需要查看容器内应用产生的数据&#xff0c;或者需要将容器内数据进行备份&#xff0c;甚至多个容器之间进行数据共享&#xff0c;这必然会涉及到容器的数据管理&#xff1a; &#xff08;1&#xff…

(含代码)利用NVIDIA Triton加速Stable Diffusion XL推理速度

在 NVIDIA AI 推理平台上使用 Stable Diffusion XL 生成令人惊叹的图像 扩散模型正在改变跨行业的创意工作流程。 这些模型通过去噪扩散技术迭代地将随机噪声塑造成人工智能生成的艺术&#xff0c;从而基于简单的文本或图像输入生成令人惊叹的图像。 这可以应用于许多企业用例&…

【剑指offer--C/C++】JZ25 合并两个排序的链表

题目 思路 这个题目大逻辑比较简单&#xff0c;就是一个比较和穿插&#xff0c;但细节上要考虑清楚&#xff0c;可以画个图模拟一下。我这里是设置将两个链表拆开组成一个新的链表&#xff0c;这样不需要占用新的空间。两个指针对应节点的值进行比较&#xff0c;那个节点值较小…

GEE——如何在谷歌地球引擎中获取二进制概率的准确性?(含具体代码介绍)

如何在谷歌地球引擎中获取二进制概率的准确性? Receiver Operating Characteristic(ROC)简介 Receiver Operating Characteristic(ROC)曲线是一种描述分类模型性能的图形工具。在二元分类问题中,ROC曲线以假正例率(False Positive Rate, FPR)为横坐标,真正例率(Tru…

【Stable Diffusion】入门-03:图生图基本步骤+参数解读

目录 1 图生图原理2 基本步骤2.1 导入图片2.2 书写提示词2.3 参数调整 3 随机种子的含义4 拓展应用 1 图生图原理 当提示词不足以表达你的想法&#xff0c;或者你希望以一个更为简单清晰的方式传递一些要求的时候&#xff0c;可以给AI输入一张图片&#xff0c;此时图片和文字是…