热点技术:React性能优化总结

简介:

初学者对React可能满怀期待,觉得React可能完爆其它一切框架,甚至不切实际地认为React可能连原生的渲染都能完爆——对框架的狂热确实会出现这样的不切实际的期待。让我们来看看React的官方是怎么说的。React官方文档在Advanced Performanec这一节,这样写道:

 
 
  1. One of the first questions people ask when considering React for a project is whether their application will be as fast and responsive as an equivalent non-React version 

显然React自己也其实只是想尽量达到跟非React版本相若的性能,

你所不知道的render

react的组件渲染分为初始化渲染和更新渲染。在初始化渲染的时候会调用根组件下的所有组件的render方法进行渲染,如下图(绿色表示已渲染,这一层是没有问题的):

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

我们的理想状态是只调用关键路径上组件的render,如下图:

但是react的默认做法是调用所有组件的render,再对生成的虚拟DOM进行对比,如不变则不进行更新。这样的render和虚拟DOM的对比明显是在浪费,如下图(黄色表示浪费的render和虚拟DOM对比)

Tips:

  • 拆分组件是有利于复用和组件优化的。

  • 生成虚拟DOM并进行比对发生在render()后,而不是render()前。

更新阶段的生命周期

  • componentWillReceiveProps(object nextProps):当挂载的组件接收到新的props时被调用。此方法应该被用于比较this.props 和 nextProps以用于使用this.setState()执行状态转换。(组件内部数据有变化,使用state,但是在更新阶段又要在props改变的时候改变state,则在这个生命周期里面)

  • shouldComponentUpdate(object nextProps, object nextState): -boolean 当组件决定任何改变是否要更新到DOM时被调用。作为一个优化实现比较this.props 和 nextProps 、this.state 和 nextState ,如果React应该跳过更新,返回false。

  • componentWillUpdate(object nextProps, object nextState):在更新发生前被立即调用。你不能在此调用this.setState()

  • componentDidUpdate(object prevProps, object prevState): 在更新发生后被立即调用。(可以在DOM更新完之后,做一些收尾的工作)

Tips:

  • React的优化是基于shouldComponentUpdate的,该生命周期默认返回true,所以一旦prop或state有任何变化,都会引起重新render。

shouldComponentUpdate

react在每个组件生命周期更新的时候都会调用一个shouldComponentUpdate(nextProps, nextState)函数。它的职责就是返回true或false,true表示需要更新,false表示不需要,默认返回为true,即便你没有显示地定义 shouldComponentUpdate 函数。这就不难解释上面发生的资源浪费了。

为了进一步说明问题,我们再引用一张官网的图来解释,如下图( SCU表示shouldComponentUpdate,绿色表示返回true(需要更新),红色表示返回false(不需要更新);vDOMEq表示虚拟DOM比对,绿色表示一致(不需要更新),红色表示发生改变(需要更新)):

根据渲染流程,首先会判断shouldComponentUpdate(SCU)是否需要更新。如果需要更新,则调用组件的render生成新的虚拟DOM,然后再与旧的虚拟DOM对比(vDOMEq),如果对比一致就不更新,如果对比不同,则根据最小粒度改变去更新DOM;如果SCU不需要更新,则直接保持不变,同时其子元素也保持不变。

  • C1根节点,绿色SCU (true),表示需要更新,然后vDOMEq红色,表示虚拟DOM不一致,需要更新。

  • C2节点,红色SCU (false),表示不需要更新,所以C4,C5均不再进行检查

  • C3节点同C1,需要更新

  • C6节点,绿色SCU (true),表示需要更新,然后vDOMEq红色,表示虚拟DOM不一致,更新DOM。

  • C7节点同C2

  • C8节点,绿色SCU (true),表示需要更新,然后vDOMEq绿色,表示虚拟DOM一致,不更新DOM。

带坑的写法:

  • {…this.props} (不要滥用,请只传递component需要的props,传得太多,或者层次传得太深,都会加重shouldComponentUpdate里面的数据比较负担,因此,也请慎用spread attributes(<Component {…props} />))。

  • ::this.handleChange()。(请将方法的bind一律置于constructor)

  • this.handleChange.bind(this,id)

  • 复杂的页面不要在一个组件里面写完。

  • 请尽量使用const element。

  • map里面添加key,并且key不要使用index(可变的)。具体可参考使用Perf工具研究React Key对渲染的影响

  • 尽量少用setTimeOut或不可控的refs、DOM操作。

  • 数据尽可能简单明了,扁平化。

性能检测工具

React官方提供的:React.addons.Perf

react官方提供一个插件React.addons.Perf可以帮助我们分析组件的性能,以确定是否需要优化。
打开console面板,先输入Perf.start()执行一些组件操作,引起数据变动,组件更新,然后输入Perf.stop()。(建议一次只执行一个操作,好进行分析)
再输入Perf.printInclusive查看所有涉及到的组件render,如下图(官方图片):

或者输入Perf.printWasted()查看下不需要的的浪费组件render,如下图(官方图片):

优化前:

优化后:

其他的检测工具

react-perf-tool为React应用提供了一种可视化的性能检测方案,该工程同样是基于React.addons,但是使用图表来显示结果,更加方便。

React官方的解决方案

PureRenderMixin(es5)

 
  1. var PureRenderMixin = require('react-addons-pure-render-mixin'); 
  2. React.createClass({ 
  3.   mixins: [PureRenderMixin], 
  4.  
  5.   render: function() { 
  6.     return <div className={this.props.className}>foo</div>; 
  7.   } 
  8. }); 

Shallow Compare (es6)

 
  1. var shallowCompare = require('react-addons-shallow-compare'); 
  2. export class SampleComponent extends React.Component { 
  3.   shouldComponentUpdate(nextProps, nextState) { 
  4.     return shallowCompare(this, nextProps, nextState); 
  5.   } 
  6.  
  7.   render() { 
  8.     return <div className={this.props.className}>foo</div>; 
  9.   } 

es7装饰器的写法:

 
  1. import pureRender from "pure-render-decorator" ... @pureRender class Person extends Component { render() { console.log("我re-render了"); const {name,age} = this.props; return ( <div> <span>姓名:</span> <span>{name}</span> <span> age:</span> <span>{age}</span> </div> 
  2.       ) 
  3.   } 

pureRender很简单,就是把传进来的component的shouldComponentUpdate给重写掉了,原来的shouldComponentUpdate,无论怎样都是return ture,现在不了,我要用shallowCompare比一比,shallowCompare代码及其简单,如下:

 
  1. function shallowCompare(instance, nextProps, nextState) { 
  2.   return !shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState); 

缺点

shallowEqual其实只比较props的第一层子属性是不是相同,就像上述代码,props 是如下

 
  1.   detail: { 
  2.     name: "123", age: "123" 
  3.   } 

他只会比较props.detail ===nextProps.detail,导致在传入复杂的数据的情况下,优化失效。

immutable.js

我们也可以在 shouldComponentUpdate() 中使用使用 deepCopy 和 deepCompare 来避免无必要的 render(),但 deepCopy 和 deepCompare 一般都是非常耗性能的。

Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。

Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。请看下面动画:

Immutable 则提供了简洁高效的判断数据是否变化的方法,只需 === 和 is 比较就能知道是否需要执行 render(),而这个操作几乎 0 成本,所以可以极大提高性能。修改后的 shouldComponentUpdate 是这样的:

 
  1. import { is } from 'immutable'; 
  2.  
  3. shouldComponentUpdate: (nextProps = {}, nextState = {}) => { 
  4.   const thisProps = this.props || {}, thisState = this.state || {}; 
  5.  
  6.   if (Object.keys(thisProps).length !== Object.keys(nextProps).length || 
  7.       Object.keys(thisState).length !== Object.keys(nextState).length) { 
  8.     return true; 
  9.   } 
  10.  
  11.   for (const key in nextProps) { 
  12.     if (!is(thisProps[key], nextProps[key])) { 
  13.       return true; 
  14.     } 
  15.   } 
  16.  
  17.   for (const key in nextState) { 
  18.     if (thisState[key] !== nextState[key] || !is(thisState[key], nextState[key])) { 
  19.       return true; 
  20.     } 
  21.   } 
  22.   return false; 
  23.  
  24. react-immutable-render-mixin 

这是一个facebook/immutable-js的react pure render mixin 的库,可以简化很多写法。
使用react-immutable-render-mixin可以实现装饰器的写法。

 
  1. import React from 'react'; 
  2. import { immutableRenderDecorator } from 'react-immutable-render-mixin'; 
  3.  
  4. @immutableRenderDecorator 
  5. class Test extends React.Component { 
  6.   render() { 
  7.     return <div></div>; 
  8.   } 

Immutable 详解及 React 中实践

无状态组件

为了避免一定程度的浪费,react官方还在0.14版本中加入了无状态组件,如下:

 
  1. // es6 
  2. const HelloMessage = (props) => <div>Hello {props.name}</div>; 

高阶组件(接下来的方向)

大部分使用mixin和class extends的地方,高阶组件都是更好的方案——毕竟组合优于继承

参考文章

使用ES6编写React应用(4):使用高阶组件替代Mixins
Mixin 已死,Composition 万岁

React同构直出(接下来方向)

同构基于服务端渲染,却不止是服务端渲染。

React在减少重复渲染方面确实是有一套独特的处理办法,那就是virtual DOM,但显示在首次渲染的时候React绝无可能超越原生的速度。因此,我们在做优化的时候,接下来可以做的事情就是:

首屏时间可能会比较原生的慢一些,但可以尝试用React Server Render (又称Isomorphic)去提高效率


作者:Pines_Cheng

来源:51CTO

相关文章
|
2月前
|
前端开发 JavaScript UED
使用React Hooks优化前端应用性能
本文将深入探讨如何使用React Hooks来优化前端应用的性能,重点介绍Hooks在状态管理、副作用处理和组件逻辑复用方面的应用。通过本文的指导,读者将了解到如何利用React Hooks提升前端应用的响应速度和用户体验。
|
2天前
|
前端开发 JavaScript 开发者
【专栏:HTML与CSS前端技术趋势篇】前端框架(React/Vue/Angular)与HTML/CSS的结合使用
【4月更文挑战第30天】前端框架React、Vue和Angular助力UI开发,通过组件化、状态管理和虚拟DOM提升效率。这些框架与HTML/CSS结合,使用模板语法、样式管理及组件化思想。未来趋势包括框架简化、Web组件标准采用和CSS在框架中角色的演变。开发者需紧跟技术发展,掌握新工具,提升开发效能。
|
2天前
|
JavaScript 前端开发 开发者
【TypeScript技术专栏】TypeScript与React的完美结合
【4月更文挑战第30天】React和TypeScript在前端开发中备受推崇。React以其组件化、高性能和灵活的生态系统引领UI构建,而TypeScript通过静态类型检查和面向对象特性增强了代码的健壮性和可维护性。两者结合,能提升开发效率,降低错误,使React组件结构更清晰。通过安装TypeScript,配置tsconfig.json,然后用TypeScript编写和打包代码,可实现两者的无缝集成。这种结合为前端开发带来更强的代码质量和团队协作效果,随着技术发展,其应用将更加广泛。
|
2天前
|
开发框架 Dart 前端开发
【Flutter前端技术开发专栏】Flutter与React Native的对比与选择
【4月更文挑战第30天】对比 Flutter(Dart,强类型,Google支持,快速热重载,高性能渲染)与 React Native(JavaScript,庞大生态,热重载,依赖原生渲染),文章讨论了开发语言、生态系统、性能、开发体验、学习曲线、社区支持及项目选择因素。两者各有优势,选择取决于项目需求、团队技能和长期维护考虑。参考文献包括官方文档和性能比较文章。
【Flutter前端技术开发专栏】Flutter与React Native的对比与选择
|
3月前
|
JavaScript 前端开发 算法
React与Vue性能对比
React与Vue性能对比
|
3月前
|
JavaScript 前端开发
React与Vue性能对比:两大前端框架的性能
React与Vue性能对比:两大前端框架的性能
|
3月前
|
JavaScript 前端开发
React与Vue性能对比
React与Vue性能对比
22 2
|
3月前
|
前端开发 JavaScript
React渲染性能的优化
React渲染性能的优化
27 2
|
4月前
|
移动开发 前端开发 JavaScript
探究移动端混合开发技术:React Native、Weex、Flutter的比较与选择
移动端混合开发技术在移动应用开发领域日益流行,为开发者提供了更高效的跨平台开发方案。本文将比较三种主流混合开发技术:React Native、Weex和Flutter,从性能、生态系统和开发体验等方面进行评估,以帮助开发者在选择适合自己项目的技术时做出明智的决策。
|
4月前
|
移动开发 前端开发 weex
React Native、Weex、Flutter 混合开发技术的比较与选择
移动应用已经成为人们日常生活中不可或缺的一部分,而混合开发技术也随之崛起并逐渐成为主流。本文将比较 React Native、Weex 和 Flutter 三种混合开发技术,并探讨它们各自的优缺点,以及如何根据项目需求做出选择。
58 1