【React性能优化总结】React组件什么时候重新渲染?如何做性能优化?

本文只讨论函数式组件,因为React不再建议使用class component。

过渡期完成以后,函数式组件成为正统的React的开发模型,"component"就用来指代function component,而classes会使用“legacy class components”的方式来指代。未来不会出现“必须使用class组件才能实现”的React行为。

React组件什么时候重新渲染

热身练习:对于下面这个demo,你能够不看实际输出,预测点击每个button分别会产生的结果吗?哪几个组件会重新渲染?为什么是这个结果?
Codesandbox demo

通过查看console.log的输出,你能够知道哪几个组件实际re-render了。从这个例子可以分析出React的重渲染的规律:

(1). 当Parent re-render时,Child也会re-render,除非以下2种情况之一发生:

  • 父组件渲染的JSX tree中,在某个位置使用的<Child/>对象(react element,即React.createElement()返回的对象)与上次渲染时相同(在同一个位置使用同一个对象引用),这时Child组件不会re-render。
  • Child组件被React.memo装饰,并且传给Child组件的props与上次渲染相同。

    • 这种情况可以理解为React.memo帮我们缓存了上次渲染时使用的<Child/>jsx element对象。即
const Component = React.memo(({count}) => {
     console.log('Component re-render')
     return <div>Count: {count}</div>
})

等价于

const Component = ({count}) => {
        return React.useMemo(() => {
                console.log('Component re-render')
                return <div>Count: {count}</div>
        }, [count]) // deps数组包含所有Parent可能传入的props value
}

(2). 当Child的状态更新并且re-render的时候,Parent不会re-render,只有Child以及它的后裔组件会re-render。并且Child组件re-render的时候,收到的props与上次渲染相同
(3). 如果通过setState设置的新state与当前state相同,则不会触发re-render。(bail out)

如何做性能优化

基于上面总结的React重渲染规律,我们可以推导出以下的性能优化方式:

  • 使用React.memo装饰组件,使得它在props相同的时候不重新渲染(它下面的子树也不会重新渲染)。
  • 使用React.usememo来缓存计算结果、或者jsx element。它有2个方面的作用:

    • 一方面,能够避免重复计算。当依赖与上次相同的时候,计算函数不会被调用,而是直接取上次缓存的计算结果。
    • 另一方面,可以用来避免子组件的re-render。前面已经说了,如果<Child/>这个jsx element对象与上次渲染是同一个对象,那么Child就不会被重新渲染(它的render函数不会被调用)。因此,Parent可以使用React.usememo来缓存上次渲染时使用的<Child/>jsx element对象,避免Child组件重新渲染。
  • Parent主动优化,给Child传入相同的props.someJSX(Parent可以使用useMemo来避免Child的props值变化)。当Child组件将props.someJSX渲染出来的时候,React会发现这个React element与上次渲染时相同,于是不会尝试diff这个React element下面的节点

    • 因此,如果Parent知道Child的props.someJSX是不需要更新的,可以传递一个缓存的值给Child,避免这颗子树的diff和更新。props.children经常能够这样优化。
  • 如果Parent根本就没有重新渲染,re-render的起点是Child,此时Child的所有props必定与上次渲染相同。这种情况下,如果Child组件将props.someJSX渲染出来,React会发现这个React element与上次渲染时相同,于是不会尝试diff这个React element下面的节点。props.children经常能够满足这种条件。

扩展阅读

redux-react作者编写的React渲染总结


csRyan的学习专栏
分享对于计算机科学的学习和思考,只发布有价值的文章: 对于那些网上已经有完整资料,且相关资料已经整...

So you're passionate? How passionate? What actions does your passion lead you to do? If the heart...

1.1k 声望
181 粉丝
0 条评论
推荐阅读
手写一个Parser - 代码简单而功能强大的Pratt Parsing
在编译的流程中,一个很重要的步骤是语法分析(又称解析,Parsing)。解析器(Parser)负责将Token流转化为抽象语法树(AST)。这篇文章介绍一种Parser的实现算法:Pratt Parsing,又称Top Down Operator Precede...

csRyan阅读 2.7k

手把手教你写一份优质的前端技术简历
不知不觉一年一度的秋招又来了,你收获了哪些大厂的面试邀约,又拿了多少offer呢?你身边是不是有挺多人技术比你差,但是却拿到了很多大厂的offer呢?其实,要想面试拿offer,首先要过得了简历那一关。如果一份简...

tonychen152阅读 17.7k评论 5

封面图
正则表达式实例
收集在业务中经常使用的正则表达式实例,方便以后进行查找,减少工作量。常用正则表达式实例1. 校验基本日期格式 {代码...} {代码...} 2. 校验密码强度密码的强度必须是包含大小写字母和数字的组合,不能使用特殊...

寒青56阅读 8.4k评论 11

JavaScript有用的代码片段和trick
平时工作过程中可以用到的实用代码集棉。判断对象否为空 {代码...} 浮点数取整 {代码...} 注意:前三种方法只适用于32个位整数,对于负数的处理上和Math.floor是不同的。 {代码...} 生成6位数字验证码 {代码...} ...

jenemy48阅读 7k评论 12

从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
总结截止到本章 “从零搭建 Node.js 企业级 Web 服务器” 主题共计 16 章内容就更新完毕了,回顾第零章曾写道:搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项这几件必须做好的关键事项就...

乌柏木75阅读 7.1k评论 16

再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...

libinfs42阅读 6.8k评论 12

封面图
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
分层规范从本章起,正式进入企业级 Web 服务器核心内容。通常,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,如下图:从上至下,抽象层次逐渐加深。从下至上,业务细节逐渐清晰。视图...

乌柏木45阅读 8.5k评论 6

So you're passionate? How passionate? What actions does your passion lead you to do? If the heart...

1.1k 声望
181 粉丝
宣传栏