当前位置: 首页 > Web前端 > HTML

React性能优化总结

时间:2023-03-28 10:49:18 HTML

前言目的目前在工作中,大量的项目都是使用React进行的。了解react的性能优化,对项目体验和可维护性都有很大的好处。下面介绍这里有一些可以在react中使用的性能优化方法;性能优化思路对于类组件和功能组件,可以从以下几个方面思考如何优化性能,减少重新渲染次数,减少渲染节点数,合理设计渲染计算量,减少渲染计算量重新渲染。react中最耗时的地方之一就是reconciliation(reconciliation的最终目的是根据新状态以最有效的方式更新UI,我们可以简单理解为diff),如果不执行render,不需要reconciliation,可见在性能优化过程中减少render的重要性。PureComponentReact.PureComponent与React.Component非常相似。两者的区别在于React.Component没有实现shouldComponentUpdate(),而React.PureComponent通过浅比较prop和state来实现这个功能。需要注意的是,在使用PureComponent的组件中,当props或者state的属性值为对象时,无法避免不必要的渲染,因为自动加载的shouldComponentUpdate只是做浅比较,所以想使用PureComponent的特性应该遵循的原则:保证数据类型是值类型。如果是引用类型,应该没有深度的数据变化(解构)。ShouldComponentUpdate可以使用此事件来决定何时需要重新渲染组件。如果组件props发生变化或调用了setState,则此函数返回一个布尔值,如果为true将重新渲染组件,否则不会重新渲染组件。在这两种情况下,前端训练组件都会重新呈现。我们可以在这个生命周期事件中放置一个自定义逻辑来决定是否调用组件的渲染函数。举个小例子来帮助理解:比如在你的应用中显示学生的详细信息,每个学生包含多个属性,如姓名、年龄、爱好、身高、体重、家庭住址、父母姓名等;在这个组件场景下,只需要显示学生的姓名、年龄和地址,其他信息不需要在这里显示,所以理想情况下,除了姓名、年龄和地址之外的组件不需要重新渲染示例代码如下:importReactfrom"react";exportdefaultclassShouldComponentUpdateUsageextendsReact.Component{constructor(props){super(props);this.state={name:"Xiaoming",age:12,address:"xxxxxx",height:165,weight:40}}componentDidMount(){setTimeout(()=>{this.setState({height:168,weight:45});},5000)}shouldComponentUpdate(nextProps,nextState){if(nextState.name!==this.state.name||nextState.age!==this.state.age||nextState.address!==this.state.address){返回真;}returnfalse;}render(){const{姓名、年龄、地址}=this.状态;return(

学生姓名:{name}

学生年龄:{age}

学生地址:{address}

)}}按照React团队的说法,shouldComponentUpdate是保证性能的紧急出口,既然是紧急情况如果有出口,就说明我们不能轻易使用,但是既然有这样的紧急出口,就说明有时候还是有必要的。所以我们必须弄清楚什么时候需要使用这个紧急出口。当你觉得改变的状态或道具不需要更新视图时,你应该考虑是否使用它。需要注意的一点是,更改后,视图的状态不需要更新,也不应该放在状态中。使用shouldComponentUpdate也是有代价的。如果处理不好,会比渲染一次更消耗性能,同时也会增加组件的复杂度。一般来说,可以使用PureComponent;React.memo如果你的组件在相同的props下渲染相同,那么你可以通过将其包装在React.memo中并通过记住组件的渲染结果来调用它来提高组件的性能。这意味着在这种情况下,React将跳过渲染组件并直接重用最近渲染的结果。React.memo只检查props的变化。如果一个函数组件被React.memo包裹,并且在其实现中有useState、useReducer或useContextHook,当状态或上下文发生变化时,它仍然会重新渲染。默认情况下,它只对复杂对象进行浅层比较。如果要控制比较过程,请传入自定义比较函数作为第二个参数。functionMyComponent(props){/使用props渲染/}functionareEqual(prevProps,nextProps){/*如果将nextProps传入render方法的返回结果与将prevProps传入render方法的返回结果一致,则返回true,否则返回false*/}exportdefaultReact.memo(MyComponent,areEqual);请注意,与类组件中的shouldComponentUpdate()方法不同,如果props相等,areEqual将返回true;如果道具不相等,它将返回false。这与shouldComponentUpdate方法的返回值相反。正确使用ContextContext提供了一种在组件树之间传递数据的方法,而无需手动为每一层组件添加props。因为这个特性,可以通过React.memo或shouldComponentUpdate的比较,即一旦Context的Value发生变化,所有依赖于该Context的组件都会forceUpdate。这与Mobx和Vue系统的响应性不同,ContextAPI无法细粒度地检测出哪些组件依赖于哪些状态。PrincipleContext只定义了大多数组件共享的属性,例如当前用户信息、主题或选择的语言。避免使用匿名函数先看下面的代码constMenuContainer=({list})=>({list.map((i)=>(handleClick(i.id)}value={i.value}/>))});上面的写法看似比较简洁,但是有一个潜在的问题就是匿名函数每次渲染都会有不同的引用,从而导致Menu组件重复渲染的问题;useCallback可用于优化:constMenuContainer=({list})=>{consthandleClick=useCallback((id)=>()=>{//...},[]);return({list.map((i)=>())});};减少渲染节点组件懒加载组件懒加载可以让react应用在真正需要显示这个组件的时候才显示这个组件,可见可以有效的减少渲染节点的数量,提高页面的加载速度。React在16.6版本之后正式引入了新特性:React.lazy和React.Suspense。这两个组件的结合可以方便组件的懒加载。执行;React.lazy方法的主要作用是定义一个动态加载的组件,可以直接减少打包bundle的体积,并且可以在第一次渲染时延迟加载不需要渲染的组件。代码示例如下:在使用之前importSomeComponentfrom'./SomeComponent';使用constSomeComponent=React.lazy(()=>import('./SomeComponent'));使用React.lazy的动态导入特性需要JS环境支持Promise。在IE11及以下浏览器中,需要polyfill才能使用此功能。React.Suspense组件的主要作用是配合惰性组件的渲染,让加载元素在等待惰性组件加载的同时显示出来,不会直接空白,提升用户体验;Suspense组件中的fallback属性接受任何组件加载过程中你想要显示的React元素。您可以将Suspense组件放置在惰性组件之上的任何位置,您甚至可以用一个Suspense组件包装多个惰性组件。代码示例如下:importReact,{Suspense}from'react';constOtherComponent=React.lazy(()=>import('./OtherComponent'));constAnotherComponent=React.lazy(()=>import('./AnotherComponent'));functionMyComponent(){return(
Loading...
}>
);}需要注意一点:React.lazy和Suspense技术还不支持服务端渲染。如果要在使用服务端渲染的应用中使用,推荐使用库LoadableComponents,可以结合这篇文档服务端渲染打包指南查看。另外,业界还有一些比较成熟的react组件懒加载开源库:react-loadable和react-lazyload,有兴趣的可以一起看看;Virtuallist虚拟列表是一种基于滚动容器元素可见区域渲染长列表的方式列表数据中某部分数据的技术,在一些项目的开发中,会遇到一些没有通过分页直接加载列表数据的场景。这种情况下可以考虑结合虚拟列表进行优化,可以根据容器元素的内容来实现。列表项元素的高度和高度用于显示长列表数据的某一部分,而不是完全渲染长列表,以提高无限滚动的性能。可以关注两个常用类库的下放,深入了解react-virtualizedreact-window减少渲染计算量useMemo先来看useMemo的基本用法:functioncomputeExpensiveValue(a,b){//一些有大量计算的逻辑返回xxx}constmemoizedValue=useMemo(computeExpensiveValue,[a,b]);useMemo的第一个参数是一个函数,这个函数返回的值会被缓存起来,这个值会作为useMemo的返回值,第二个第一个参数是一个数组依赖。如果数组中的值发生变化,则重新执行第一个参数中的函数,并将函数返回的值缓存起来,作为useMemo的返回值。请注意,如果未提供依赖数组,useMemo将在每次渲染时计算一个新值;如果计算是一个小的计算函数,你也可以选择不使用useMemo,因为这种优化不会是性能瓶颈的点,反而可能使用不当也会造成一些性能问题。在遍历演示视图时使用keykeys有助于React识别哪些元素发生了变化,例如添加或删除。所以你应该给数组中的每个元素一个明确的标识符。常量数字=[1,2,3,4,5];constlistItems=numbers.map((number)=>{number});usekey注意:最好这个元素在列表中有一个唯一的字符串。通常,我们使用数据中的id作为元素的key。当元素没有特定的id时,不得已可以使用元素索引index作为key。元素的键只有放在最近的数组上下文中才有意义。例如,如果提取一个ListItem组件,则应将键保留在数组中的这个元素上,而不是在ListItem组件中的元素上。合理设计组件,简化道具。如果组件的props比较复杂,会影响shallowCompare的效率,导致组件难以维护。另外也不符合“单一职责”的原则,可以考虑拆解。简化状态在设计组件的状态时,可以遵循这样的原则:组件需要响应它的变化或者需要渲染到视图中的数据应该放在状态中;这可以避免导致组件重新渲染的不必要的数据更改。减少组件嵌套一般不必要的节点嵌套是由于滥用高阶组件/RenderProps造成的。所以还是那句话'只在必要时才使用xxx'。替换高阶组件/RenderProps的方式有很多,比如先用props,ReactHooks