前言

老早就想写一篇关于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. 02-C高级编程

    Day01 笔记 1 typedef使用 1.1 起别名 - 简化struct关键字 1.2 区分数据类型 1.3 提高代码移植性 2 void使用 2.1 不可以利用void创建变量 无法给无类型变 ...

  2. WC2015 题解

    K小割 题目链接:WC2015 K小割 Description 题目很清楚了,已经不能说的更简洁了-- Solution 这道题出题人挺毒的,你需要针对不同的部分分施用不同的做法 . 第\(1\)部分 ...

  3. 零成本搭建个人博客之图床和cdn加速

    本文属于零成本搭建个人博客指南系列 为什么要使用图床 博客文章中的图片资源文件一般采用本地相对/绝对路径引用,或者使用图床通过外链进行引用展示.本地引用的弊端我认为在于: 图片和博客放在同一个代码托管 ...

  4. 5-5配置Mysql复制 基于日志点的复制

    配置MySQL复制 基于日志点的复制配置步骤 设置简单密码(可以选择不需要) set GLOBAL validate_password_length=6; set global validate_pa ...

  5. camunda如何调用HTTP REST(Service Task)服务节点

    ​ Camunda中的Service Task(服务任务)用于调用服务.在Camunda中,可以通过调用本地Java代码.外部工作项.web服务形式实现的逻辑来完成的. 本文重点描述如何使用web服务 ...

  6. 前端ES6 特性兼容查询

    ES6 http://kangax.github.io/compat-table/es6/ ES5 http://kangax.github.io/compat-table/es5/ ES 2016+ ...

  7. 在VMware Workstation 16上安装Windows7虚拟机以及VMware tools安装失败解决方法

    安装VMware Workstation 16 搜素"VMware Workstation下载" 下载 VMware Workstation Pro 下载Windows7系统镜像 ...

  8. kali渗透测试阅读目录

    一.渗透测试介绍 渗透测试介绍及渗透环境配置 二.信息收集 kali 信息收集 三.漏洞扫描 kali 漏洞扫描 四.漏洞利用 kali msf漏洞利用

  9. MySQL数据检索时,sql查询的结果如何加上序号

    1.sql语法 @i:类型java定义的变量 @i:=0:这里类似给i初始化值为0 @i:=@i+1 :每次从0开始递增+1 SELECT (@i:=@i+1) as id,TDLINE FROM Y ...

  10. Linux YUM yum-utils 模块详解

    yum-utils 详解 yum-utils是yum的工具包集合,由不同的作者开发,使yum使用起来更加方便和强大.包括:debuginfo-install,find-repos-of-install ...