前言

老早就想写一篇关于React渲染的文章,这两天看到一篇比较不错英文的文章,翻译一下(主要是谷歌翻译,手动狗头),文章底部会附上原文链接。

介绍

React 重新渲染的综合指南。该指南解释了什么是重新渲染,什么是必要的和不必要的重新渲染,什么情况下会触发 React 组件重新渲染。

还包括可以防止重新渲染重要的模式和一些导致不必要的重新渲染和性能不佳的反模式。每个模式和反模式都附有图片指引和工作代码示例。

内容

React 的重新渲染是什么?

在谈论 React 性能时,我们需要关注两个主要阶段:

  • 初始渲染- 当组件首次出现在屏幕上时发生
  • 重新渲染- 已经在屏幕上的组件的第二次和任何连续渲染

当 React 需要使用一些新数据更新应用程序时,会发生重新渲染。通常,这是由于用户与应用程序交互或通过异步请求或某些订阅模型传入的一些外部数据而发生的。

没有任何异步数据更新的非交互式应用永远不会重新渲染,因此不需要关心重新渲染性能优化。

必要和不必要的重新渲染是什么?

必要的重新渲染- 重新渲染作为更改源的组件,或直接使用新信息的组件。例如,如果用户在输入字段中键入内容,则管理其状态的组件需要在每次击键时更新自身,即重新渲染。

不必要的重新渲染- 由于错误或低效的应用程序架构,应用程序通过不同的重新渲染机制导致组件的重新渲染。例如,如果用户在输入字段中键入,并且在每次击键时重新呈现整个页面,则该页面已被不必要地重新呈现。

不必要的重新渲染本身不是问题:React 非常快,通常能够在用户没有注意到任何事情的情况下处理它们。

但是,如果重新渲染发生得太频繁和/或在非常重的组件上发生,这可能会导致用户体验出现“滞后”,每次交互都会出现明显的延迟,甚至应用程序变得完全没有响应。

React 组件什么时候会重新渲染自己?

组件自身重新渲染有四个原因:状态更改、父(或子)重新渲染、上下文更改和hooks更改。还有一个很大的误区:当组件的props发生变化时会发生重新渲染。就其本身而言,它是不正确的(参见下面的解释)。

重新渲染的原因:状态变化

当组件的状态发生变化时,它会重新渲染自己。通常,它发生在回调或useEffecthooks中。

状态变化是所有重新渲染的“根”源。

重新渲染的原因:父级重新渲染

如果父组件重新渲染,组件将重新渲染自己。或者,如果我们从相反的方向来看:当一个组件重新渲染时,它也会重新渲染它的所有子组件。

它总是从根向下渲染,子的重新渲染不会触发父的重新渲染。(这里有一些警告和边缘情况,请参阅完整指南了解更多详细信息:React Element、children、parents 和 re-renders 的奥秘)。

重新渲染的原因:context 变化

当 Context Provider 中的值发生变化时,所有使用此 Context 的组件都将重新渲染,即使它们不直接使用数据变化的部分。这些重新渲染无法通过直接memo来防止,但是有一些可以模拟的变通方法

参见【第 7 部分:防止由 Context 引起的重新渲染】

重新渲染的原因:hooks变化

hooks内发生的所有事情都“属于”使用它的组件。关于conext和状态变化的相同规则适用于此:

  • hooks内的状态更改将触发不可避免的宿主重复渲染
  • 如果hooks使用了 Context 并且 Context 的值发生了变化,它会触发一个不可避免的重复渲染

hooks可以是链式的。链中的每个钩子仍然属于宿主,同样的规则适用于它们中的任何一个。

重新渲染的原因:props的变化(很大的一个误区)

当谈到没有被memo包裹的组件重新渲染,组件的props是否改变并不重要。

为了改变 props,它们需要由父组件更新。这意味着父组件必须重新渲染,这将触发子组件的重新渲染,而不管props是什么。

只有当使用momo技术(React.memouseMemo)时,props的变化才变得重要。

防止合成重复渲染?

反模式:在渲染函数中创建组件

在另一个组件的渲染函数中创建组件是一种反模式,可能是最大的性能杀手。在每次重新渲染时,React 都会重新安装这个组件(即销毁它并从头开始重新创建它),这将比正常的重新渲染慢得多。最重要的是,这将导致以下错误:

  • 重新渲染期间可能出现内容“闪烁”
  • 每次重新渲染时都会在组件中重置状态
  • useEffect 每次重新渲染都不会触发依赖项
  • 如果一个组件被聚焦,焦点将丢失

需要阅读的其他资源:如何编写高性能的 React 代码:规则、模式、注意事项

向下移动状态

当一个重量级组件管理状态,并且这个状态只用于呈现树的一小部分时,这种模式会很有用。一个典型的例子是在呈现页面大部分内容的复杂组件中通过单击按钮打开/关闭对话框。

在这种情况下,控制模态对话框外观的状态、对话框本身以及触发更新的按钮都可以封装在一个更小的组件中。因此,较大的组件不会在这些状态更改时重新渲染。

children 作为 props

这也可以称为“包裹状态作为children”。这种模式类似于“下移状态”:它将状态变化封装在一个较小的组件中。这里的区别在于状态用于包装渲染树的缓慢部分的元素,因此不能那么容易地使用它。一个典型的例子是附加到组件根元素的回调onScrollonMouseMove

在这种情况下,可以将状态管理和使用该状态的组件提取到一个较小的组件中,并将VerySlowComponent组件作为children. 从较小的组件角度来看,子组件只是props,所以它们不会受到状态变化的影响,因此不会重新渲染。

组件作为props

与之前的模式几乎相同,具有相同的行为:它将状态封装在一个较小的组件中,而重组件作为 props 传递给它。道具不受状态变化的影响,因此重型组件不会重新渲染。

当一些重量级组件独立于状态,但不能作为一个组作为子级提取时,它可能很有用。

使用 React.memo 防止重新渲染

在React中包装组件。Memo将停止在渲染树的下游重新渲染,除非这个组件的props发生了变化。

当渲染一个不依赖于重渲染源(例如,状态,更改的数据)的重渲染组件时,这是很有用的。

组件有 props

所有不是原始值的props都必须被useMemo,以便 React.memo 工作

组件作为props或children

React.memo必须应用于作为children或props传递的元素。memo父组件将不工作:子组件和props将是对象,因此它们会随着每次重新渲染而改变。

使用 useMemo/useCallback 提高重新渲染性能

反模式:props 上不必要的 useMemo/useCallback

记忆的props不会阻止子组件的重新渲染。如果父组件重新渲染,它将触发子组件的重新渲染,而不管其props如何。

必要的 useMemo/useCallback

如果一个子组件被React.memo包裹,所有不是值类型的props都必须被记忆。

useEffect如果在一个组件中, 之类的hooks中使用非值类型作为依赖项。

则应该使用useMemouseCallback对其进行记忆。

使用Memo 进行昂贵的计算

其中一个用例useMemo是避免每次重新渲染时进行昂贵的计算。

useMemo有它的成本(消耗一些内存并使初始渲染稍微慢一些),所以它不应该用于每次计算。在 React 中,在大多数情况下,安装和更新组件将是最昂贵的计算(除非您实际上是在计算素数,否则无论如何都不应该在前端进行)。

因此,典型的用例useMemo是记忆 React 元素。通常是现有渲染树的一部分或生成的渲染树的结果,例如返回新元素的映射函数。

与组件更新相比,“纯”javascript 操作(如排序或过滤数组)的成本通常可以忽略不计。

提高列表的重新渲染性能

除了常规的重新渲染规则和模式之外,该key属性还会影响 React 中列表的性能。

重要提示:仅提供key属性不会提高列表的性能。为了防止重新呈现列表元素,您需要将它们包装起来React.memo并遵循其所有最佳实践。

值作为key应该是一个字符串,这在列表中每个元素的重新渲染之间是一致的。通常,使用项目id 或数组index

可以使用数组index作为键,如果列表是静态的,即不添加/删除/插入/重新排序元素。

在动态列表上使用数组的索引会导致:

  • 如果项目具有状态或任何不受控制的元素(如表单输入),则会出现错误
  • 如果项目包装在 React.memo 中,性能会下降

在此处阅读有关此内容的更多详细信息:React 关键属性:性能列表的最佳实践

反模式:随机值作为列表中的键

随机生成的值永远不应用作key列表中属性的值。它们将导致 React 在每次重新渲染时重新安装项目,这将导致:

  • 列表的表现很差
  • 如果项目具有状态或任何不受控制的元素(如表单输入),则会出现错误

防止由Context引起的重新渲染

记忆 Provider 值

如果 Context Provider 不是放在应用程序的最根目录,并且由于其祖先的更改,它可能会重新渲染自身,则应该记住它的值。

拆分数据和 API

如果在 Context 中存在数据和 API(getter 和 setter)的组合,则它们可以拆分为同一组件下的不同 Provider。这样,使用 API 的组件仅在数据更改时不会重新渲染。

在此处阅读有关此模式的更多信息:如何使用 Context 编写高性能的 React 应用程序

将数据分成块

如果 Context 管理一些独立的数据块,它们可以被拆分为同一个提供者下的更小的提供者。这样,只有更改块的消费者才会重新渲染。

Context selectors (上下文选择器)

使用部分 Context 值的没有办法阻止组件重新渲染,即使使用的数据没有更改,即使使用useMemo钩子也是如此。

然而,上下文选择器可以通过使用高阶组件和React.memo.

在此处阅读有关此模式的更多信息:React Hooks 时代的高阶组件

结束语

已上的内容来自 # React re-renders guide: everything, all at once. 英文好的同学可以直接看原文更佳。

如果你觉得该文章不错,不妨

1、点赞,让更多的人也能看到这篇内容

2、关注我,让我们成为长期关系

3、关注公众号「前端有话说」,里面已有多篇原创文章,和开发工具,欢迎各位的关注,第一时间阅读我的文章

React重新渲染指南的更多相关文章

  1. (转)2019年 React 新手学习指南 – 从 React 学习线路图说开去

    原文:https://www.html.cn/archives/10111 注:本文根据 React 开发者学习线路图(2018) 结构编写了很多新手如何学习 React 的建议.2019 年有标题党 ...

  2. 【独家】React Native 版本升级指南

    前言 React Native 作为一款跨端框架,有一个最让人头疼的问题,那就是版本更新.尤其是遇到大版本更新,JavaScript.iOS 和 Android 三端的配置构建文件都有非常大的变动,有 ...

  3. 基于React服务器端渲染的博客系统

    系统目录及源码由此进入 目录 1. 开发前准备 1.1 技术选型1.2 整体设计1.3 构建开发 2. 技术点 2.1 react2.2 redux, react-router2.3 server-r ...

  4. react+redux渲染性能优化原理

    大家都知道,react的一个痛点就是非父子关系的组件之间的通信,其官方文档对此也并不避讳: For communication between two components that don't ha ...

  5. React服务器渲染最佳实践

    源码地址:https://github.com/skyFi/dva-starter React服务器渲染最佳实践 dva-starter 完美使用 dva react react-router,最好用 ...

  6. 2017.11.7 ant design - upload 组件的使用, react 条件渲染以及 axios.all() 的使用

    一.主要任务:悉尼小程序管理后台,添加景点页面的开发 二.所遇问题及解决 1. 上传多个不同分类音频信息时,如中文音频和英文音频,要求音频不是放在一个数组中的,每个音频是一个独立的字段,此时: < ...

  7. 玩转 React 服务器端渲染

    React 提供了两个方法 renderToString 和 renderToStaticMarkup 用来将组件(Virtual DOM)输出成 HTML 字符串,这是 React 服务器端渲染的基 ...

  8. React 服务器渲染原理解析与实践

    第1章 服务器端渲染基础本章主要讲解客户端与服务器端渲染的概念,分析客户端渲染和服务器端渲染的利弊,带大家对服务器端渲染有一个粗浅认识. 1-1 课程导学1-2 什么是服务器端渲染1-3 什么是客户端 ...

  9. react 16 渲染整理

    背景 老的react架构在渲染时会有一些性能问题,从setstate到render,程序一直在跑,一直到render完成.才能继续下一步操作.如果组件比较多,或者有复杂的计算逻辑,这之间的消耗的时间是 ...

随机推荐

  1. 一次生产环境的docker MySQL故障

    问题 昨天下午本来要去吃下午茶,然后前端小伙伴突然说接口怎么崩了,我登上sentry一看,报错了 (2005, "Unknown MySQL server host 'mysql' (-3) ...

  2. vue3 迫不得已我硬着头皮查看了keepalive的源代码,解决了线上的问题

    1.通过本文可以了解到vue3 keepalive功能 2.通过本文可以了解到vue3 keepalive使用场景 3.通过本文可以学习到vue3 keepalive真实的使用过程 4.通过本文可以学 ...

  3. Mac下iTerm2安装rzsz后上传下载失败解决

    背景描述 mac环境,安装了iTerm2,需要使用ssh登陆linux服务器.服务器登陆需要经过以下步骤 输入token 输入登陆选项 输入IP 因此写了expect脚本来完成自动输入 但是在上传下载 ...

  4. django框架4

    内容概要 编辑删除功能编写 虚拟环境 django路由层版本区别 视图函数的返回值 JsonResponse对象 form表单上传文件 request其他方法 FBV与CBV(基于函数的视图.基于类的 ...

  5. 即时通讯IM,是时代进步的逆流?看看JNPF怎么说

    JNPF快速开发平台所包含的第四个重要的开发框架是即时通讯沟通工具.即时沟通工具的目的是让各大企事业单位在各种业务工作流程环境下实现实时无缝协同办公,打破信息数据孤岛,形成高效的层级流转审批和各流程环 ...

  6. Fiddler对安卓高版本进行抓包解决方案以及分析 进阶二

    今天是2021年的最后一天了,多分享一些干货吧!看过上一章节教程后会有同学疑惑,我也一步一个脚印的,跟着流程走也设置了代理以及安装了证书,有的同学会发现 为什么手机不能够连接网络了呢?细心一点的同学会 ...

  7. 软件成分分析(SCA)完全指南

    上一篇文章中,我们讨论了 DAST 的概念.重要性及其工作原理.那在开发过程中如何查找开源软件包中的漏洞并学习如何修复?本指南带你一起了解 SCA 工具及其最佳实践. 如今,绝大多数代码驱动的应用程序 ...

  8. 20天等待,申请终于通过,安装和体验IntelliJ IDEA新UI预览版

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 关于IDEA的预览版 IDEA会启用新的UI,这事情之 ...

  9. python基础知识-day9(库学习)

    1.os学习 1 print(os.name) #获取操作系统 2 print(os.path.exists("D:\soft\python")) #判断路径是否存在 3 prin ...

  10. .NET ORM框架HiSql实战-第一章-集成HiSql

    一.引言 做.Net这么多年,出现了很多很多ORM框架,比如Dapper,Sqlsugar,Freesql等等.在之前的项目中,用到的ORM框架也大多数是这几个老牌的框架. 不过最近园子关于.NET ...