这才是ReactHooks性能优化的正确姿势
时间:2023-03-20 21:53:50
科技观察
ReactHooks出来很久了,相信很多小伙伴都已经深入使用了。无论是React本身还是它的生态都在摸索着进步。鉴于我使用React的经验,在此分享给大家。对于Reacthooks,性能优化可以从以下几个方面考虑。场景一使用ReactHooks后,很多人会抱怨渲染次数变多了。没错,官方是这么建议的:我们建议把状态分成多个状态变量,每个变量包含的不同值会同时变化。functionBox(){const[position,setPosition]=useState({left:0,top:0});const[size,setSize]=useState({width:100,height:100});//...}这种异步情况下的写法,比如调用接口返回后,同时setPosition和setSize,相对于class的写法,会多出一个render。这是效果图数量增加的根本原因。当然这种写法还是比较推荐的,可读性和可维护性更高,逻辑分离性更好。对于这种场景,如果useState多了十几个或者几十个,可读性会变差,这时候就需要对依赖进行组件化。逻辑导向,在不同文件中分离,使用React.memo屏蔽其他状态导致的重渲染。constPosition=React.memo(({position}:PositionProps)=>{//位置相关逻辑return(
{position.left}
);});所以在Reacthooks组件中尽量不要写pipeline,代码最好保持在200行左右,通过组件化降低耦合和复杂度,优化一定的性能。场景2类比较钩子,上面代码:classCounterextendsReact.Component{state={count:0,};increment=()=>{this.setState((prev)=>({count:prev.count+1,}));};render(){const{count}=this.state;return
;}}functionCounter(){const[count,setCount]=React.useState(0);functionincrement(){setCount((n)=>n+1);}return
;}直观上,你觉得hooks是不是等同于class写法??错了,hooks的写法已经埋下坑了。当计数状态更新时,会重新执行Counter组件,此时会重新创建一个新的函数增量。这样每次传给ChildComponent的onClick都是一个新的函数,导致ChildComponent组件的React.memo失效。解决方案:functionusePersistFn
any>(fn:T){constref=React.useRef(()=>{thrownewError('Cannotcallfunctionwhilerendering.');});ref.current=fn;returnReact.useCallback(ref.currentasT,[ref]);}//推荐使用`usePersistFn`constincrement=usePersistFn(()=>{setCount((n)=>n+1);});//或使用useCallbackconstincrement=React.useCallback(()=>{setCount((n)=>n+1);},[]);上面声明了usePersistFn自定义hook,可以保证函数地址??在这个组件中永不改变。完美解决useCallback依赖值变化重新生成新函数的问题。强烈推荐逻辑量大的组件使用。不仅仅是函数,比如每次渲染创建的新对象,在传递给子组件时都会出现这样的问题。组件的参数上尽量不要传递render创建的对象,比如style={{width:0}}之类的代码,使用React.useMemo或者React.memo写equal函数来优化。如果不需要更改样式,则可以将其提取出来并在组件外部声明。虽然这种写法感觉过于繁琐,但是不依赖于React.memo重新实现,是一种优化性能的有效手段。conststyle:React.CSSProperties={width:100};functionCustomComponent(){return;}场景三对于复杂场景,使用useWhyDidYouUpdatehook调试当前变量变量导致的重渲染。这个函数也可以直接使用ahooks中的实现。functionuseWhyDidYouUpdate(name,props){constpreviousProps=useRef();useEffect(()=>{if(previousProps.current){installKeys=Object.keys({...previousProps.current,...props});constchangesObj={};allKeys.forEach(key=>{if(previousProps.current[key]!==props[key]){changesObj[key]={from:previousProps.current[key],to:props[key]};}});if(Object.keys(changesObj).length){console.log('[why-did-you-update]',name,changesObj);}}previousProps.current=props;});}constCounter=React.memo(props=>{useWhyDidYouUpdate('Counter',props);return{props.count} ;});当useWhyDidYouUpdate中监听的props发生变化时,会打印对应的值比较。调试神器,强烈推荐。场景4使用ChromePerformance代码调试,记录一次操作,分析Timings标签中耗时最长的逻辑在哪里,展示组件的层级堆栈,然后精准优化。场景5强烈推荐用于React中的函数式编程,它允许将数据不可变性用作优化手段。我在Reactclass时代大量使用immutable.js结合redux来搭建业务。它与React中的PureComponent完美配合,并保持了非常好的性能。但是在Reacthooks中结合TypeScript就显得有点格格不入,类型支持也不是很完善。这里可以试试immer.js,引入成本小,写法也简单很多。constnextState=produce(currentState,(draft)=>{draft.p.x.push(2);})//truecurrentState===nextState;场景6复杂场景使用Map对象代替数组操作,map.get(),map.has(),相比数组查找效率特别高。//Mapconstmap=newMap([['a',{id:'a'}],['b',{id:'b'}],['c',{id:'c'}]]);//查找值map.has('a');//获取值map.get('a');//遍历map.forEach(n=>n);//可以很方便的转换为Array.from(map.values());//数组constlist=[{id:'a'},{id:'b'},{id:'c'}];//查找值list.some(n=>n.id==='a');//取值list.find(n=>n.id==='a');//遍历list.forEach(n=>n);结语React性能调优,除了防止重渲染外,还与代码编写方式有关。最后想推一下最近写的React状态管理库https://github.com/MinJieLiu/heo,也可以作为性能优化的手段。希望大家可以从redux的繁琐中解脱出来,把省下的时间用来享受生活。