当前位置: 首页 > 科技观察

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

时间:2023-03-17 16:22:49 科技观察

初学者可能对React充满期待,认为React可能会打爆其他所有框架,甚至不切实际地认为React甚至可能会打爆原生渲染——对框架的狂热真是太不现实了期望出现。我们来看看React官方是怎么说的。React官方文档在AdvancedPerformanec部分,内容如下:人们在考虑将React用于项目时提出的第一个问题是他们的应用程序是否与等效的非React版本一样快速和响应显然React本身只是想尝试实现与非React版本相似的性能。你所不知道的renderReact的组件渲染分为初始渲染和更新渲染。初始化渲染时,会调用根组件下所有组件的render方法进行渲染,如下图(绿色表示已经渲染过,这一层没问题):但是当我们想要updateasubcomponent,如下图Greencomponent(根组件传下来的数据应用到greencomponent的变化):我们的理想状态是只调用keypath上的component的render,如下图如下图:但是react默认的方法是调用所有组件的render,然后比较生成的虚拟DOM,如果不变则不更新。这样render和virtualDOM的比较明显是浪费,如下图(黄色表示render和virtualDOM比较浪费)Tips:拆分组件有利于复用和组件优化。虚拟DOM在render()之后生成和比较,而不是在render()之前。更新阶段的生命周期componentWillReceiveProps(objectnextProps):当挂载的组件接收到新的props时调用。此方法应该用于比较this.props和nextProps以使用this.setState()执行状态转换。(组件内部数据发生变化,使用state,但是在update阶段和props变化时改变state,那么在这个生命周期)shouldComponentUpdate(objectnextProps,objectnextState):-boolean组件决定时是否更新到达DOM时调用的任何更改。作为优化,该实现将this.props与nextProps、this.state与nextState进行比较,如果React应该跳过更新则返回false。componentWillUpdate(objectnextProps,objectnextState):在更新发生之前立即调用。你不能在这里调用this.setState()。componentDidUpdate(objectprevProps,objectprevState):更新发生后立即调用。(DOM更新后可以做一些收尾工作)Tips:React的优化是基于shouldComponentUpdate,在这个生命周期内默认返回true,所以prop或state的任何变化都会引起重新渲染。shouldComponentUpdatereact在每个组件生命周期更新时调用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和C5C3节点和C1一样,需要更新C6节点,绿色SCU(true),表示需要更新,vDOMEq为红色,表示虚拟DOM不一致,更新DOM。C7节点和C2C8节点一样,绿色SCU(true)表示需要更新,然后vDOMEq为绿色,表示虚拟DOM一致,DOM未更新。写的有坑:{…this.props}(不要滥用,请只传组件需要的props,如果传的太多,或者层次太深,会增加shouldComponentUpdate中的数据负担,所以请谨慎使用spreadattributes())。::这。句柄变化()。(请将方法的绑定放在构造函数中)this.handleChange.bind(this,id)不要在一个组件中编写复杂的页面。请尝试使用const元素。给map加一个key,key不能用index(变量)。具体可以参考使用Perf工具研究ReactKey对渲染的影响。尽量少用setTimeOut或不可控的refs和DOM操作。数据尽量简单明了,扁平化。React官方提供的性能检测工具:React.addons.Perfreact官方提供了一个插件React.addons.Perf,可以帮助我们分析组件的性能,判断是否需要优化。打开控制台面板,先进入Perf.start()进行一些组件操作,引起数据变化,组件更新,然后进入Perf.stop()。(建议一次只执行一个操作,方便分析)然后输入Perf.printInclusive查看所有涉及的组件渲染,如下图(官图):或??输入Perf.printWasted()查看不必要的浪费组件渲染,如下图(官图):优化前:优化后:其他检测工具react-perf-tool为React应用提供了可视化的性能检测方案。这个项目也是基于React.addons,但是使用图表来展示结果。更方便。React官方解决方案PureRenderMixin(es5)varPureRenderMixin=require('react-addons-pure-render-mixin');React.createClass({mixins:[PureRenderMixin],render:function(){returnfoo

;}});浅比较(es6)varshallowCompare=require('react-addons-shallow-compare');exportclassSampleComponenttextendsReact.Component{shouldComponentUpdate(nextProps,nextState){returnshallowCompare(this,nextProps,nextState);}render(){returnfoo
;}}es7装饰器的写法:importpureRenderfrom"pure-render-decorator"...@pureRenderclassPersonextendsComponent{render(){console.log("我重新渲染");const{name,age}=this.props;return(
name:{name}{name}age:{age}
)}}pureRender很简单,就是重写传入组件的shouldComponentUpdate,原来的shouldComponentUpdate,反正是returnture,不是现在,我要用sshallowCompare代码很简单,如下:functionshallowCompare(instance,nextProps,nextState){return!shallowEqual(instance.props,nextProps)||!shallowEqual(instance.state,nextState);}的缺点shallowEqual其实只是比较而已一级道具的子属性一样吗?就像上面的代码,props是这样的{detail:{name:"123",age:"123"}},它只会比较props.detail===nextProps.detail,结果,在这种情况下传入复??杂数据,优化失败immutable.js我们也可以在shouldComponentUpdate()中使用deepCopy和deepCompare来避免不必要的render(),但是deepCopy和deepCompare一般都非常耗性能。不可变数据是一旦创建就无法更改的数据。对Immutable对象的任何修改或添加或删除都会返回一个新的Immutable对象。Immutable实现的原理是PersistentDataStructure(持久化数据结构),即在使用旧数据创建新数据时,需要同时保证旧数据可用且不变。同时,为了避免deepCopy复制所有节点带来的性能损失,Immutable使用了StructuralSharing(结构共享),即如果对象树中的一个节点发生变化,只有这个节点和受其影响的父节点被修改,然后其他节点共享。请看下面的动画:Immutable提供了一种简单高效的方法来判断数据是否发生了变化。你只需要比较===和is就知道你是否需要执行render(),而这个操作成本几乎为0,因此可以大大提高性能。.修改后的shouldComponentUpdate是这样的:import{is}from'immutable';shouldComponentUpdate:(nextProps={},nextState={})=>{constthisProps=this.props||{},thisState=this.state||{};if(Object.keys(thisProps).length!==Object.keys(nextProps).length||Object.keys(thisState).length!==Object.keys(nextState).length){returntrue;}for(constkeyinnextProps){if(!is(thisProps[key],nextProps[key])){returntrue;}}for(constkeyinnextState){if(thisState[key]!==nextState[key]||!is(thisState[key],nextState[key])){returntrue;}}returnfalse;}react-immutable-render-mixin这是一个facebook/immutable-jsreactpurerendermixin库,可以简化很多写法。使用react-immutable-render-mixin可以实现装饰器的写法。importReactfrom'react';import{immutableRenderDecorator}from'react-immutable-render-mixin';@immutableRenderDecoratorclassTestextendsReact.Component{render(){return
;}}React中的不可变细节和无状态组件的实践为了避免某些浪费程度,React官方在0.14版本加入了无状态组件,如下://es6constHelloMessage=(props)=>
Hello{props.name}
;high-levelcomponents(nextdirection)大部分mixin和classextends用到的地方,high-ordercomponents是更好的解决方案——毕竟组合比继承好。参考文章WritingReactapplicationswithES6(4):Usinghigh-levelcomponentsinsteadMixinsMixinisdead,CompositionLongliveReact同构直出(下一个方向)同构基于服务端渲染,但不仅仅是服务端渲染。React确实有一个独特的减少重复渲染的方法,那就是虚拟DOM,但是这说明React在第一次渲染的时候是绝对不可能超越原生速度的。所以,当我们在做优化的时候,接下来我们可以做的是:首屏时间可能会比原来慢,但是我们可以尝试使用ReactServerRender(也就是Isomorphic)来提高效率