前言目的目前在工作中,大量的项目都是使用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})=>(
);上面的写法看似比较简洁,但是有一个潜在的问题就是匿名函数每次渲染都会有不同的引用,从而导致Menu组件重复渲染的问题;useCallback可用于优化:constMenuContainer=({list})=>{consthandleClick=useCallback((id)=>()=>{//...},[]);return(
);}需要注意一点: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)=>