前言目的目前在工作中,大量的项目都是使用React进行的,了解和掌握React的性能优化对于项目的体验和可维护性有很大的好处,下面介绍一些可以在React中使用的性能优化方法;性能优化思路对于类组件和函数式组件,可以从以下几个方面来思考如何优化性能,减少重新渲染的次数,减少渲染Reconciliation是React中最耗时的部分(reconciliation的最终目标是以最高效的方式根据新状态更新UI,我们可以简单理解为diff),如果不执行Render,则不需要Reconciliation,可见降低Render在性能优化过程中的重要性.PureComponentReact.PureComponent与React.Component非常相似。两者的区别在于React.Component没有实现shouldComponentUpdate(),而React.PureComponent通过浅对比Prop和State_front-end训练实现了这个功能。需要注意的是,在使用PureComponent的组件中,当Props或者State的属性值为对象时,无法避免不必要的渲染,因为自动加载的shouldComponentUpdate只是做浅比较,所以想使用PureComponent的特性应该遵循的原则:保证数据类型是值类型。如果是引用类型,应该没有深度的数据变化(解构)。ShouldComponentUpdate可以使用此事件来决定何时需要重新渲染组件。此函数返回一个布尔值,如果组件props发生变化或调用setState,将重新渲染组件,否则不会重新渲染组件。在这两种情况下,组件都会重新渲染。我们可以在这个生命周期事件中放置一个自定义逻辑来决定是否调用组件的Render函数。举个小例子来帮助理解:比如在你的应用中显示学生的详细信息,每个学生包含多个属性,如姓名、年龄、爱好、身高、体重、家庭住址、父母姓名等;在这个组件场景下,只需要显示学生的姓名、年龄和地址,其他信息不需要在这里显示,所以理想情况下,除了姓名、年龄和地址之外的组件不需要重新渲染示例代码如下: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){returntrue;}returnfalse;}render(){const{名称,年龄,地址}=this.state;return(
学生姓名:{name}
学生年龄:{age}
学生地址:{address}
)}}按照React团队的说法,shouldComponentUpdate是保证性能的紧急出口。既然是紧急出口,那就意味着我们不能随便使用。但是既然有这样一个紧急出口,就说明有时候还是有必要的。所以我们必须弄清楚什么时候需要使用这个紧急出口。当你觉得改变的State或者Props不需要更新视图的时候,你就应该考虑是否使用它。需要注意的一点是,更改后,不需要更新视图的状态,也不要放在State中。使用shouldComponentUpdate也是有代价的。如果处理不好,会比多一个Render消耗更多的性能,同时也会增加组件的复杂度。一般来说,可以使用PureComponent;React.memo如果你的组件在相同的Props下渲染相同的结果,你可以通过将其包装在React.memo中并通过记住组件的渲染结果来调用它来提高组件的性能。这意味着在这种情况下,React将跳过渲染组件并直接重用最近渲染的结果。React.memo只检查props的变化。如果一个函数组件被React.memo包裹,并且在其实现中有useState、useReducer或useContextHook,当State或Context发生变化时,它仍然会重新渲染。默认情况下,它只对复杂对象进行浅层比较。如果要控制比较过程,请传入自定义比较函数作为第二个参数。functionMyComponent(props){/使用props渲染//functionareEqual(prevProps,nextProps){/*如果将nextProps传入render方法的返回结果与将prevProps传入render方法的返回结果一致,则返回true,否则returnfalse*/}exportdefaultReact.memo(MyComponent,areEqual);注意:与Class组件中的shouldComponentUpdate()方法不同,如果Props相等,areEqual会返回true;如果Props不相等,它将返回false。这与shouldComponentUpdate方法的返回值相反。正确使用ContextContext提供了一种在组件树之间传递数据的方法,而无需手动为每一层组件添加props。因为这个特性,可以通过React.memo或者shouldComponentUpdate的比较,也就是说一旦Context的Value发生变化,所有依赖于Context的组件都会强制更新。这与Mobx和Vue系统的响应性不同,ContextAPI无法细粒度地检测哪些组件依赖于哪些states_web前端训练。PrincipleContext只定义了大多数组件共享的属性,例如当前用户信息、主题或选择的语言。避免使用匿名函数先看下面的代码constMenuContainer=({list})=>(
);上面的写法看似比较简洁,但是有一个潜在的问题就是匿名函数每次渲染都会有不同的引用,从而导致Menu组件重复渲染的问题;useCallback可用于优化:constMenuContainer=({list})=>{consthandleClick=useCallback((id)=>()=>{//...},[]);return(
);};减少渲染节点组件懒加载组件懒加载可以让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,有兴趣的可以一起看看;virtuallistvirtuallist是一种根据滚动容器元素的可见区域来渲染长列表的方法数据中某一部分数据的技术,在一些项目的开发中,会遇到一些列表数据是不是通过分页直接加载的。对于这种情况,可以结合虚拟列表进行优化,可以实现容器元素的高度和列表项元素的高度来显示长列表的某一部分数据,而不是完全渲染长列表来提高无限滚动的表现。可以关注两个常用类库的下放,深入了解react-virtualizedreact-window减少渲染计算量useMemo先来看看useMemo的基本用法:functioncomputeExpensiveValue(a,b){//一些计算量很大的逻辑returnxxx}constmemoizedValue=useMemo(computeExpensiveValue,[a,b]);useMemo的第一个参数是一个函数,这个函数返回的值会被缓存起来,这个值会作为useMemo的返回值,第二个参数是一个数组依赖。如果数组中的值发生变化,则重新执行第一个参数中的函数,并将函数返回的值缓存起来,作为useMemo的返回值。请注意,如果未提供依赖数组,useMemo将在每次渲染时计算一个新值;如果计算是一个小的计算函数,你也可以选择不使用useMemo,因为这种优化不会是性能瓶颈的点,反而可能使用不当也会造成一些性能问题。在遍历演示视图时使用keykeys有助于React识别哪些元素发生了变化,例如添加或删除。所以你应该给数组中的每个元素一个明确的标识符。常量数字=[1,2,3,4,5];constlistItems=numbers.map((number)=>