当前位置: 首页 > news >正文

从性能角度看react组件拆分的重要性

react性能瓶颈

借图说话,例如下图是一个组件结构tree,当我们要更新某个子组件的时候,如下图的绿色组件(从根组件传递下来应用在绿色组件上的数据发生改变):

理想情况下,我们只希望关键路径上的组件进行更新,如下图:

但是,实际效果却是每个组件都完成re-rendervirtual-DOM diff过程,虽然组件没有变更,这明显是一种浪费。如下图黄色部分表示浪费的re-render和virtual-DOM diff。

根据上面的分析,react的性能瓶颈主要表现在:

对于propsstate没有变化的组件,react也要重新生成虚拟DOM及虚拟DOM的diff。

shouldComponentUpdate来进行性能优化

针对react的性能瓶颈,我们可以通过react提供的shouldComponentUpdate方法来做点优化的事,可以有选择的进行组件更新,从而提升react的性能,具体如下:

shouldComponentUpdate需要判断当前属性和状态是否和上一次的相同,如果相同则不需要执行后续生成虚拟DOM及其diff的过程,否则需要更新。

具体可以这么显示实现:

shouldComponentUpdate(nextProps, nextState){ return !isEqual(nextProps, this.props) || !isEqual(nextState, this.state) }

其中,isEqual方法为判断两个对象是否相等(指的是其对象内容相等,而不是全等)。

通过显示覆盖shouldComponentUpdate方法来判断组件是否需要更新从而避免无用的更新,但是若为每个组件添加该方法会显得繁琐,好在react提供了官方的解决方案,具体做法:

方案对组件的shouldComponentUpdate进行了封装处理,实现对组件的当前属性和状态与上一次的进行浅对比,从而决定组件是否需要更新。

react在发展的不同阶段提供两套官方方案:

  • PureRenderMin
    一种是基于ES5的React.createClass创建的组件,配合该形式下的mixins方式来组合PureRenderMixin提供的shouldComponentUpdate方法。当然用ES6创建的组件也能使用该方案。
import PureRenderMixin from 'react-addons-pure-render-mixin'; class Example extends React.Component { constructor(props) { super(props); this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); }
  • PureComponent
    该方案是在React 15.3.0版本发布的针对ES6而增加的一个组件基类:React.PureComponent。这明显对ES6方式创建的组件更加友好。
import React, { PureComponent } from 'react' class Example extends PureComponent { render() { // ... } }

需要指出的是,不管是PureRenderMin还是PureComponent,他们内部的shouldComponentUpdate方法都是浅比较(shallowCompare)propsstate对象的,即只比较对象的第一层的属性及其值是不是相同。例如下面state对象变更为如下值:

state = { value: { foo: 'bar' } }

因为state的value被赋予另一个对象,使nextState.valuethis.props.value始终不等,导致浅比较通过不了。在实际项目中,这种嵌套的对象结果是很常见的,如果使用PureRenderMin或者PureComponent方式时起不到应有的效果。

虽然可以通过深比较方式来判断,但是深比较类似于深拷贝,递归操作,性能开销比较大。

为此,可以对组件尽可能的拆分,使组件的propsstate对象数据达到扁平化,结合着使用PureRenderMin或者PureComponent来判断组件是否更新,可以更好地提升react的性能,不需要开发人员过多关心。

组件拆分

组件拆分,在react中就是将组件尽可能的细分,便于复用和优化。拆分的具体原则:

  • 尽量使拆分后的组件更容易判断是否更新

这不太好理解,举个例子吧:假设我们定义一个父组件,其包含了5000个子组件。有一个输入框输入操作,每次输入一个数字,对应的那个子组件背景色变红。

<div> <input value={this.state.inputText} onChange={this.inputChanged}/> <ul { this.state.items.map(el=> <li key={el.id} style={{background: index===this.state.inputText? 'red' : ''}}>{el.name}</li> } </ul> </div>

本例中,输入框组件和列表子组件有着明显的不同,一个是动态的,输入值比较频繁;一个是相对静态的,不管input怎么输入它就是5000项。输入框每输入一个数字都会导致所有组件re-render,这样就会造成列表子组件不必要的更新。

可以看出,上面列表组件的更新不容易被取消,因为输入组件和列表子组件的状态都置于父组件state中,二者共享;react不可能用shouldComponentUpdate的返回值来使组件一部分组件更新,另一部分不更新。 只有把他们拆分为不同的组件,每个组件只关心对应的props。拆分的列表组件只关心自己那部分属性,其他组件导致父组件的更新在列表组件中可以通过判断自己关心的属性值情况来决定是否更新,这样才能更好地进行组件优化。

  • 尽量使拆分组件的props和state数据扁平化

这主要是从组件优化的角度考虑的,如果组件不需过多关注性能,可以忽略。

拆分组件之所以扁平化,是因为React提供的优化方案PureRenderMin或者PureComponent是浅比较组件的propsstate来决定是否更新组件。

上面的列表组件中,this.state.items存放的是对象数组,为了更好的判断每项列表是否需要更新,可以将每个li列表项单独拆分为一个列表项组件,每个列表项相关的props就是items数组中的每个对象,这种扁平化数据很容易判断是否数据发生变化。

组件拆分的一个例子

为了这篇文章专门写了一个有关添加展示Todo列表的事例库。克隆代码到本地可以在本地运行效果。

该事例库是一个有着5000项的Todo列表,可以删除和新增Todo项。该事例展示了组件拆分前和拆分后的体验对比情况,可以发现有性能明显的提升。

下面我们结合react的性能检测工具react-addons-perf来说明组件拆分的情况。

拆分前的组件TodosBeforeDivision的render部分内容如下:

<input value={this.state.value} onChange={this.inputChange.bind(this)}/> <button onClick={this.addTodo.bind(this)}>add todo</button> { this.state.items.map(el=>{ return ( <TodoItem key={el.id} item={el} tags={['important', 'starred']} deleteItem={this.deleteItem.bind(this, el.id)}/>) }) }

组件拆分前,输入框输入字符、增加todo或者删除todo项可以看出有明显的卡顿现象,如下图所示:

为了弄清楚是什么原因导致卡顿现象,我们使用chrome的devTool来定位,具体的做法是使用最新版的chrome浏览器的Performance选项来完成。先点击该选项中的record按钮开始记录,这时我们在组件输入框输入一个字符,然后点击stop来停止记录,我们会看到组件从输入开始到结束这段时间内的一个性能profile。

http://www.jsqmd.com/news/1105784/

相关文章:

  • Spring Boot 源码研读之创建DefaultBootstrapContext并执行BootstrapRegistryInitializer.initialize()
  • 一站式批量图片翻译与智能抠图提升工作效率
  • Spring Boot 源码研读之 SpringApplication 对象的创建
  • 大规模服务集成中的限流设计:保护上游也保护业务
  • 宇宙常数即超复数空间广义分形维数统一猜想及实例论证
  • 检测 win10 硬件部分的 powershell
  • 计算机Java毕设实战-基于 Java 的学术文献资源分类检索系统的设计与实现 基于 Java 的数字化文献资料归档管理系统【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • AI 搜索新时代,好客搜智搜 GEO 系统搭建企业长效 AI 全域运营渠道
  • Pixel2Geo单目视觉解算协同增量网格渲染:像素驱动高精度空间重建优化算法
  • Kafka 高可用架构:副本数不是越多越安全
  • 原生一体化渲染管线破算力卡顿桎梏,全域像素同源融合消实景画面割裂难题
  • DeFi 协议收益率数学模型与风险量化分析
  • 微软Memora如何破解智能体的长期记忆难题
  • 像素几何映射与分布式3D图形渲染耦合架构:广域视频孪生动态世界模型构建研究
  • 一站式企业数字化运营平台,解读好客搜全产品线协同技术优势
  • 2026年度智能编码工具深度横评:引入Coding Agent的团队,人均代码吞吐量提升35%以上
  • 为什么途鸽求职的求职辅导效果这么好?
  • 小众且实用,这软件是真神器!
  • MH迈汇:从公开信息出发,拆解风控思路与流程清晰度
  • 初等数学研究教材PDF电子版分享
  • 企业级检索增强 后端集成:Java 服务如何管理知识库版本
  • 抖音无水印下载终极指南:5分钟学会批量下载高清视频的完整教程
  • Python数据库编程实战:从psycopg3到SQLAlchemy Core — PostgreSQL篇
  • PMSM在线转动惯量辩识+滑模负载转矩观测器(仿真+参考文献+理论公式文档)
  • 别再“面向百度编程”了!AI 时代,我们要做“指挥家”而不是“搬砖工”
  • 开发者必读:openeuler/cdf-crypto API接口全解析(附代码示例)
  • HarmonyOS NEXT ArkTS 背景图片模式实战——Fill / Contain / Cover 三种缩放模式深度解析
  • 如何在Windows 11 LTSC系统一键安装微软商店:完整指南
  • Windows 11g在线库迁移及搭建双机
  • STM32寄存器开发练习(二):GPIO的工作模式