讲清楚 React 的重新渲染

2023-11-05

Web 前端开发者对渲染和重新渲染应该不陌生,在 React 中,它们究竟是什么意思?

  • 渲染:React 让组件根据当前的 props 和 state 描述它要展示的内容。
  • 重新渲染:React 让组件重新描述它要展示的内容。

要将组件显示到屏幕上,React 的工作主要分为两个阶段,本文介绍与 React 渲染相关的知识。

  • render 阶段(渲染阶段):计算组件的输出并收集所有需要应用到 DOM 上的变更。
  • commit 阶段(提交阶段):将 render 阶段计算出的变更应用到 DOM 上。

在 commit 阶段 React 会更新 DOM 节点和组件实例的 ref,如果是类组件,React会同步运行 componentDidMount 或 componentDidUpdate 生命周期方法,如果是函数组件,React会同步运行 useLayoutEffect 勾子,当浏览器绘制 DOM 之后,再运行所有的 useEffect 勾子。

React 重新渲染

初始化渲染之后,下面的这些原因会让React重新渲染组件:

1.类组件

  • 调用 this.setState 方法。
  • 调用this.forceUpdate方法。

2.函数组件

  • 调用 useState 返回的 setState。
  • 调用 useReducer 返回的 dispatch。

3.其他

  • 组件订阅的 context value 发生变更
  • 重新调用 ReactDOM.render( <AppRoot>)

假设组件树如下

默认情况,如果父组件重新渲染,那么 React 会重新渲染它所有的子组件。当用户点击组件 A 中的按钮,使 A 组件 count 状态值加1,将发生如下的渲染流程:

1.React将组件A添加到重新渲染队列中。
2.从组件树的顶部开始遍历,快速跳过不需要更新的组件。
3.React发生A组件需要更新,它会渲染A。A返回B和C
4.B没有被标记为需要更新,但由于它的父组件A被渲染了,所以React会渲染B
5.C没有被标记为需要更新,但由于它的父组件A被渲染了,所以React会渲染C,C返回D
6.D没有标记为需要更新,但由于它的父组件C被渲染了,所以D会被渲染。

在默认渲染流程中,React 不关心子组件的 props 是否改变了,它会无条件地渲染子组件。很可能上图中大多数组件会返回与上次完全相同的结果,因此 React 不需要对DOM 做任何更改,但是,React 仍然会要求组件渲染自己并对比前后两次渲染输出的结果,这两者都需要时间。

Reconciliation

Reconciliation 被称为 diff 算法,它用来比较两颗 React 元素树之间的差异,为了让组件重新渲染变得高效,React 尽可能地复用现有的组件和 DOM。为了降低时间复杂度,Diff 算法基于如下两个假设:

1.两个不同类型的元素对应的元素树完全不同。
2.在同一个列表中,如果两个元素key属性的值相同,那么它们被识别为同一个元素。

元素类型对 Diff 的影响

React 使用元素的 type 字段比较元素类型是否相同,如果两颗树在相同位置要渲染的元素类型相同,那么 React 就重用这些元素,并在适当的时候更新,不需要重新创建元素,这意味着,只要一直要求 React 将某组件渲染在相同的位置,那么 React 始终不会卸载该组件。如果相同位置的元素类型不同,例如从 div 到 span 或者从ComponentA 到 ComponentB,React会认为整个树发生了变化,为了加快比较过程,React 会销毁整个现有的组件树,包括所有的 DOM 节点,然后重新创建元素。

浏览器内置元素的 type 字段是一个字符串,自定义组件元素的 type 字段是一个类或者函数,由于元素类型对 Diff的影响,所以在渲染期间不要创建组件,只要创建一个新的组件,那么它的 type 字段就是不同的引用,这将导致 React 不断地销毁并重新创建子组件树。不要有如下的代码:

function ParentCom() {// 每一次渲染 ParentCom 时,都会创建新的ChildCom组件function ChildCom() {/**do something*/}return <ChildCom />
} 

上述代码不推荐,正确的做法是将 ChildCom 放在ParentCom 的外面。

key 对 Diff 的影响

React 识别元素的另一种方式是通过 key 属性,key 作为组件的唯一标识符不会当作prop传递到组件中,可以给任何组件添加一个 key 属性来标注它,更改 key 的值会导致旧的组件实例和 DOM 被销毁。

列表是使用 key 属性的主要场景,在 React 官方文档中提到,不要将数组的下标作为 key 值,而是用数据唯一 ID 作为 key 值。在这里分别介绍这两种方式的区别。

假如 Todo List 中有 10 项,先用数组下标作为 key 的值,这 10 项 Todo 的 key 值为 0…9,现在删除数组的第 6 项和第 7 项,并在数组末尾添加 3 个新的数据项,我们最终将得到 key 值为0…10的 Todo,看起来只是在末尾新增 1 项,将原来的列表从10项变成了11项,React 很乐意复用已有的 DOM 节点和组件实例,这意味着原来 #6 对应的组件实例没有被销毁,现在它接收新的 props 用于呈现原来的 #8。在这个例子中 React 会创建 1 个Todo,更新 4 个Todo。

如果使用数据的 ID 作为 key 值,React 能发现第 6 项和第 7 项被删除了,它也能发现数组新增了 3 项,所以 React 会销毁 #6 和 #7 项对应的组件实例及其关联的 DOM,还会创建 3 个组件实例及其关联的 DOM。

提高渲染性能

要将组件显示在界面上,组件必须经历渲染流程,但渲染工作有时候会被认为是浪费时间,如果渲染的输出结果没有改变,它对应的DOM节点也不需要更新,此时与该组件相关的渲染工作真的是在浪费时间。React组件的输出结果始终基于当前 props 和 state 的值,因此,如果我们知道组件的 props 和 state 没有改变,那么我们可以无后顾之忧地让组件跳过重新渲染。

跳过重新渲染

React 提供了 3 个主要的API让我们跳过重新渲染:

  • React.Component 的 shouldComponentUpdate:这是类组件可选的生命周期函数,它在组件 render 阶段早期被调用,如果返回false,React 将跳过重新渲染该组件,使用它最常见的场景是检查组件的 props 和 state 是否自上次以来发生了变更,如果没有改变则返回false。* React.PureComponent:它在 React.Component 的基础上添加默认的 shouldComponentUpdate 去比较组件的 props 和 state 自上次渲染以来是否有变更。* React.memo():它是一个高阶组件,接收自定义组件作为参数,返回一个被包裹的组件,被包裹的组件的默认行为是检查 props 是否有更改,如果没有,则跳过重新渲染。上述方法都通过‘浅比较’来确定值是否有变更,如果通过 mutable 的方式修改状态,这些 API 会认为状态没有变。

  • 如果组件在其渲染过程中返回的元素的引用与上一次渲染时的引用完全相同,那么 React 不会重新渲染引用相同的组件。示例如下:

function ShowChildren(props: {children: React.ReactNode}) {const [count, setCount] = useState<number>(0)return (<div>{count} <button onClick={() => setCount(c => c + 1)}>click</button>{/* 写法一 */}{props.children}{/* 写法二 */}{/* <Children/> */}</div>)
} 

上述 ShowChildren 的 props.children 对应 Children 组件,因此写法一和写法二在浏览器中呈现一样。点击按钮不会让写法一的 Children 组件重新渲染,但是会使写法二的 Children 组件重新渲染。

上述4种方式跳过重新渲染意味着 React 会跳过整个子树的重新渲染。

Props 对渲染优化的影响

默认情况,只要组件重新渲染,React 会重新渲染所有被它嵌套的后代组件,即便组件的 props 没有变更。如果试图通过 React.memo 和 React.PureComponent 优化组件的渲染性能,那么要注意每个 prop 的引用是否有变更。下面的示例试图使用 React.memo 让组件不重新渲染,但事与愿违,组件会重新渲染,代码如下:

const MemoizedChildren = React.memo(Children)

function Parent() {const onClick = () => { /** todo*/}return <MemoizedChildren onClick={onClick}/>
} 

上述代码中,Parent 组件重新渲染会创建新的 onClick 函数,所以对 MemoizedChildren 而言,props.onClic k的引用有变化,最终被 React.memo 包裹的Children 会重新渲染,如果让组件跳过重新渲染对你真的很重要,那么在上述代码中将 React.memo 与 useCallback 配合使用才能达到目的。

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

讲清楚 React 的重新渲染 的相关文章

  • Vue.JS 2.5.1:未捕获的语法错误:意外的令牌导出

    我试图使用 VueJS 和 Bootstrap Vue 制作一个单选按钮 但是当我制作它时发生了这种情况 我预计这是语法错误 就像它所说的那样 但我似乎找不到任何线索 所以我尝试复制粘贴代码 这是 test radio php 的完整代码
  • 使用 获取用于 javascript 的 RSA 密钥?

    我的 Web 项目需要一个 RSA 密钥对 虽然有一些库 但我认为依靠浏览器 为了安全性和速度 为我生成密钥是个好主意 是否可以使用注册机或其他浏览器 API 来执行此操作 我不知道如何从注册机获取密钥 它们似乎是在提交时生成的 但我不想将
  • jQuery UI sortable 和 contenteditable=true 不能一起工作

    我正在创建一个列表并希望使其项目可排序和可编辑 所以我这样做 ul li span A span li li span B span li li span C span li ul ul list sortable http jsfiddl
  • 为什么“事件”在 Chrome 中全局可用,而在 Firefox 中则不然?

    在回答另一个问题时 出现了一个与event对象在匿名函数中可用 无需传入 在 Chrome 中 下面的代码工作正常 但 Firefox 会抛出错误 document ready function uspsSideboxTrackingClo
  • Google Charts(AreaChart)如何检测缩放变化

    我正在画一个面积图 在覆盖层上有一些标记 我正在使用explorer选项 仅限水平 以便用户放大和缩小 问题是我找不到一种方法来通知缩放更改 以便有机会更新制造商位置 有一个图表范围变化事件 但它不是由 AreaChart 触发的 我尝试检
  • ngx-DataTable 对列进行排序无法正常工作 Angular 4

    虽然我对角度非常陌生 但我在使用 ngx DataTable 时遇到了一些困难 我使用简单的 ngx DataTable 进行简单的操作 问题出在列上 尽管我已将 attr 声明为 sortable true 但排序不起作用 这是代码 表定
  • React/Redux bundle.js 太大

    我有一个小型的 React 项目 Webpack生成的bundle js大小为6 3Mb 如何将大小减小到 github webpack config js module exports devtool inline source map
  • jQuery 检查复选框并触发 javascript onclick 事件

    我正在尝试使用 jQuery 检查复选框并在此过程中触发 onclick 事件 假设我在 html 中定义了一个复选框
  • Rails 递归地包含 javascripts 资源文件夹

    我了解如何将一个 JavaScript 文件添加到 Rails 资产管道中 只需添加 require filename 到 application js 但是如何在一个文件夹下包含多个 javascript 文件 vendor assets
  • 更改时触发跨度文本/html

    jQuery 或 JavaScript 中是否有任何事件在以下情况下触发span标签 text html 已更改 Code span class user location span user location change functio
  • 获得一次性绑定以适用于 ng-if

    这个问题已经被之前问过 https stackoverflow com questions 23969926 angular lazy one time binding for expressions 但我无法让该解决方案发挥作用 所以我想
  • 在 jQuery AJAX 成功中从 MySql 获取特定响应

    好吧 我有这个 ajax 代码 它将在 Success 块中返回 MySql 的结果 ajax type POST url index php success function data alert data My Query sql SE
  • 浏览器默认区域设置 - Intl.DateTimeFormat 与 navigator.language

    在对网站进行编码并格式化日期时 我想使用用户在浏览器中设置的区域设置 例如 如果用户定制了他们的chrome settings languages在 Chrome 中设置为非默认值 这就是我想要使用的值 但是 当我在此类浏览器的控制台中运行
  • Phonegap facebook 插件:android 的各种问题

    我正在尝试将 Phonegap 3 1 与 Phonegap facebook plugin 集成 以使我的应用程序能够使用 facebook 登录 https github com phonegap phonegap facebook p
  • HTML 和 JavaScript - 将滚动操作从一个元素传递到另一个元素

    假设我有两个 div div div div A scrollable list div 我想让它当光标停在里面时 control并且鼠标滚轮滚动 view将会滚动 无论如何要实现这一目标 好的 快速修复对我有用 即使固定 div 不可滚动
  • toLocaleDateString() 在 Chrome 中如何工作?

    我理解了javascript方法toLocaleDateString 使用的计算机设置 让我们来W3Schools 示例 http www w3schools com jsref tryit asp filename tryjsref to
  • 数字和小数的输入掩码

    在测试我的程序后 我发现了以下错误 我在 sqlserver 中的表包含 价格数字 6 2 我的程序的用户输入价格 555 00 就很好了 但是当他输入 555555 时 这是错误的 所以我需要指定掩码 其中尾数是可选的 0 到 999 小
  • 如何从配置加载套接字 io 事件监听器? [复制]

    这个问题在这里已经有答案了 我有使用套接字io 的nodejs 应用程序 我将存储在 config routes js 中的所有事件侦听器 module exports routes auth login controller auth a
  • Apollo 客户端延迟刷新

    In Apollo Client v3React 实现 我使用钩子来使用订阅 当我从订阅接收数据时 我想重新获取查询 但前提是查询之前已执行过并且位于缓存中 有办法实现这一点吗 我首先进行惰性查询 然后在收到订阅数据时手动检查缓存 然后尝试
  • NodeJS:如何获取服务器的端口?

    您经常会看到 Node 的示例 hello world 代码 它创建一个 Http Server 开始侦听端口 然后执行以下操作 console log Server is listening on port 8000 但理想情况下你会想要

随机推荐

  • pytorch学习笔记一:pytorch学习的路线图

    1 gpu加速 tensor 和autograd 向量和自动求导 2 神经网络工具箱 gpu加速 示例代码 import torch import time print torch version print torch cuda is a
  • 嵌入式(网络编程)(网络基础)

    网络采用分层的思想 1 每一层实现不同的功能 2 每一层向上层提供服务 同时使用下层提供的服务 各层典型的协议 1 网络接口与物理层 MAC地址 48位全球唯一 网络设备的身份标识 ARP RARP ARP IP地址 gt MAC地址 RA
  • Windows10 安装wsl2、Ubuntu相关操作

    Windows10 安装wsl2 Ubuntu相关操作 安装wsl2 查看本机windows版本 键盘上按下win r 输入winver 查看系统版本 必须运行 windows 10 版本 2004 及更高版本 内部版本 19041 及更高
  • css3 vw rem 布局 最完整的 css重置 浏览器样式修复

    http caibaojian com vw vh html css重置 Reset css css浏览器样式修复 Normalize css 最新方案 Neat css neat css融合了Reset css和Normalize css
  • Python练习题一(2021.04.08)

    Python练习题一 首先不得不说 人生苦短 我用Python 作为Python的初学者 为了坚持刷题 故以每天写博客的形式砥砺自己 每天 忙的话就两天搞一次 都会总结 2 3道题目 话不多说 呈上第一天题目 题目一 在同一行依次输入三个值
  • 【H5】 meta标签适配器(兼容所有屏幕)

    H5 meta标签实现屏幕大小适配器 效果图如下 meta标签内属性解释如下 name viewport 视图窗口 content 里面写以下属性 width device width 设备宽度 initial scale 1 0 100p
  • Ubuntu下安装RocksDB

    原文地址 https program park github io 2021 06 08 rocksdb 2 安装依赖 sudo apt get install gcc g libsnappy dev zlib1g dev libbz2 d
  • HBase条件查询(多条件查询)

    Author Pirate Leo myBlog http blog csdn net pirateleo myEmail codeevoship gmail com 转载请注明出处 谢谢 文中可能涉及到的API Hadoop HDFS h
  • css中背景颜色的代码,css背景代码是什么,css怎么控制背景颜色

    css背景代码是什么 css怎么控制背景颜色 内容导读 css背景代码主要就是设置background color 然后在后面选择我们喜欢的颜色就可以了 通常css背景代码用来控制div和特殊的文本 本文举了实例 也给出了代码 大家拿去用即
  • 【springboot系】springboot集成日志框架logback

    平常我们工作中常见的日志框架 有log4j logback log4j2 logback是由log4j的创始人设计的另外一个开源日志框架 logback相比之于log4j性能提升了不少 log4j2晚于logback 也是后起之秀 官方介绍
  • 【精·超详细】Java实现图片和Base64之间的相互转化(一看就会)

    目录 一 简介 二 maven依赖 三 工具类 四 测试 一 简介 工作中调用第三方接口的时候 比如 人脸识别 身份证识别 文字识别等等 有时是图片 有时是Base64的字符串 一般前端上传的都是图片 我们有时就需要进行相应的转换了 图片转
  • VsCode下的Remote-SSH插件的使用

    0 前言 众所周知 Vs Code是一个非常NB的编辑器 它可以通过安装不同的插件 满足几乎所有的开发需求 最近了解到微软之前推出过一个Remote SSH的插件 通过该插件可以在Vs Code上通过SSH连接Linux服务器进行终端操作或
  • Non-Rule Package常用表名

    1 GL相关 Journal Template和GL BU手动添加 GL ACCOUNT TBL GP GL GROUP GP GL GROUP DTL GP GL MAP GP GL MAP DTL 2 Pay Entity GP PYE
  • ubuntu 安装mysql,postgresql, mongodb

    安装mysql mysql教程 Ubuntu系统下MySQL开启远程连接 centos安装mysql 安装过程中需要要设置root密码 sudo apt get install python dev libpython dev libpq
  • FISCO BCOS区块链搭建笔记(No.1-节点搭建)

    说明 此文章针对的是centos版本的 其他版本可以参考官网 FISCO BCOS官网链接 点此跳转 1 搭建单群组FISCO BCOS联盟链 第一步 安装依赖 安装centos依赖 sudo yum install y openssl o
  • iOS 第三方登陆 —— 微信

    一 准备工作 1 到微信开放平台注册成开发者 获取appid 2 导入WeChatConnection framework 3 配置URL Schemes 输入appid 例如wx29ce0f21ea982cb8 二 配置AppDelega
  • 机器学习之K-means聚类算法

    目录 K means聚类算法 算法流程 优点 缺点 随机点聚类 人脸聚类 旋转物体聚类 K means聚类算法 K means聚类算法是一种无监督的学习方法 通过对样本数据进行分组来发现数据内在的结构 K means的基本思想是将n个实例分
  • 栈、队列

    一 栈 栈 author Mona public class MyStack int elements public MyStack elements new int 10 压入元素 public void push int element
  • 字节跳动Data部门员工收入证明,92年女月入14 万

    评论
  • 讲清楚 React 的重新渲染

    Web 前端开发者对渲染和重新渲染应该不陌生 在 React 中 它们究竟是什么意思 渲染 React 让组件根据当前的 props 和 state 描述它要展示的内容 重新渲染 React 让组件重新描述它要展示的内容 要将组件显示到屏幕