【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.9k

安全地在前后端之间传输数据 - 「3」真的安全吗?
在「2」注册和登录示例中,我们通过非对称加密算法实现了浏览器和 Web 服务器之间的安全传输。看起来一切都很美好,但是危险就在哪里,有些人发现了,有些人嗅到了,更多人却浑然不知。就像是给门上了把好锁,还...

边城31阅读 7.1k评论 5

封面图
涨姿势了,有意思的气泡 Loading 效果
今日,群友提问,如何实现这么一个 Loading 效果:这个确实有点意思,但是这是 CSS 能够完成的?没错,这个效果中的核心气泡效果,其实借助 CSS 中的滤镜,能够比较轻松的实现,就是所需的元素可能多点。参考我们...

chokcoco19阅读 2k评论 2

在前端使用 JS 进行分类汇总
最近遇到一些同学在问 JS 中进行数据统计的问题。虽然数据统计一般会在数据库中进行,但是后端遇到需要使用程序来进行统计的情况也非常多。.NET 就为了对内存数据和数据库数据进行统一地数据处理,发明了 LINQ (L...

边城17阅读 1.9k

封面图
【已结束】SegmentFault 思否写作挑战赛!
SegmentFault 思否写作挑战赛 是思否社区新上线的系列社区活动在 2 月 8 日 正式面向社区所有用户开启;挑战赛中包含多个可供作者选择的热门技术方向,根据挑战难度分为多个等级,快来参与挑战,向更好的自己前进!

SegmentFault思否20阅读 5.6k评论 10

封面图
过滤/筛选树节点
又是树,是我跟树杠上了吗?—— 不,是树的问题太多了!🔗 相关文章推荐:使用递归遍历并转换树形数据(以 TypeScript 为例)从列表生成树 (JavaScript/TypeScript) 过滤和筛选是一个意思,都是 filter。对于列表来...

边城18阅读 7.6k评论 3

封面图
Vue2 导出excel
2020-07-15更新 excel导出安装 {代码...} src文件夹下新建一个libs文件夹,新建一个excel.js {代码...} vue页面中使用 {代码...} ===========================以下为早期的文章今天在开发的过程中需要做一个Vue的...

原谅我一生不羁放歌搞文艺14阅读 19.8k评论 9

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

1.1k 声望
181 粉丝
宣传栏