前言

老早就想写一篇关于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. 什么是请求参数、表单参数、url参数、header参数、Cookie参数?一文讲懂

    最近在工作中对 http 的请求参数解析有了进一步的认识,写个小短文记录一下. 回顾下自己的情况,大概就是:有点点网络及编程基础,只需要加深一点点对 HTTP 协议的理解就能弄明白了. 先分享一个小故 ...

  2. Python Django 功能模块

    Python Django模块 Django模块,是针对有django基础,对django功能进行模块化,方便下次使用. 一.注册模块 该注册采用邮箱验证,注册成功后会发送激活链接到邮箱. 邮箱验证参 ...

  3. CabloyJS 基于 EggJS 实现的模块编译与发布

    背景 现在,EggJS被许多开发团队所采用.有的团队基于商业知识产权的考量,往往会提一个问题:是否可以把EggJS当中的代码编译打包,然后再把代码丑化? 模块编译的机制 EggJS为何不能便利的实现编 ...

  4. 基于bat脚本的前端发布流程的优化

    背景介绍 前面在基于bat脚本的前端发布流程设计与实现中,我已经介绍了设计与实现,这一篇主要是针对其的一个优化折腾(分两步走,第一步先搞出来,第二步再想着怎么去优化它),我主要做了以下几件事. &qu ...

  5. VisonPro · 视觉工具列表说明

  6. 入行数字IC验证后会做些什么?

    半年前,公众号写了第一篇推文<入行数字IC验证的一些建议>,介绍了IC小白可以如何一步一步地摸索入门数字IC验证,同时也在知乎发了这篇入门贴,并且衍生出很多额外基础的内容,收获了不少的浏览 ...

  7. CPI教程-异步接口创建及使用

    CPI教程-异步接口创建及使用 create by yi 转载请注明出处 先简单介绍一下同步接口和异步接口 什么是同步接口 同步接口的意思就是发送方发送Message后,接口方处理完成后会立刻返回执行 ...

  8. Linux YUM 配置源

    Linux Yum 简介 YUM是交互式的以rpm为基础的软件包管理工具.YUM可以根据仓库的元数据信息,去自动的实现系统更新,包括依赖性分析,过期软件包处理.我们也可以利用yum来进行软件安装,删除 ...

  9. Day04 HTML标记

    路径 ./ 同级目录 ./ 进入该目录名下 ../ 上一级目录 HTML标记 图片 <!-- 图片标记 src 图片的路径 width 设置图片宽度 height 设置图片高度 title 鼠标 ...

  10. 基于脑波眼电-语音-APP控制的多功能智能轮椅

    前言:这个项目是在2016-2017完成的,做的很浅显,贴出来与大家分享,希望能有帮助. 摘要 本项目主要是针对脑电信号控制的智能轮椅的设计,脑电控制是智能医疗领域的重要研究方向,旨在帮助行动不便但智 ...