【React】1138- React Hooks 性能優(yōu)化的正確姿勢(shì)
前言
React Hooks?出來很長一段時(shí)間了,相信有不少的朋友已經(jīng)深度使用了。無論是?React?本身,還是其生態(tài)中,都在摸索著進(jìn)步。鑒于我使用的?React?的經(jīng)驗(yàn),給大家分享一下。對(duì)于?React hooks,性能優(yōu)化可以從以下幾個(gè)方面著手考慮。
場景1
在使用了?React Hooks?后,很多人都會(huì)抱怨渲染的次數(shù)變多了。沒錯(cuò),官方就是這么推薦的:
我們推薦把?
state?切分成多個(gè)?state?變量,每個(gè)變量包含的不同值會(huì)在同時(shí)發(fā)生變化。function Box() {const [position, setPosition] = useState({ left: 0, top: 0 });const [size, setSize] = useState({ width: 100, height: 100 });// ...}這種寫法在異步的條件下,比如調(diào)用接口返回后,同時(shí)?
setPosition?和?setSize,相較于 class 寫法會(huì)額外多出一次?render。這也就是渲染次數(shù)變多的根本原因。當(dāng)然這種寫法仍然值得推薦,可讀性和可維護(hù)性更高,能更好的邏輯分離。
針對(duì)這種場景若出現(xiàn)十幾或幾十個(gè)?useState?的時(shí)候,可讀性就會(huì)變差,這個(gè)時(shí)候就需要相關(guān)性的組件化了。以邏輯為導(dǎo)向,抽離在不同的文件中,借助?React.memo?來屏蔽其他?state?導(dǎo)致的?rerender。
const Position = React.memo(({ position }: PositionProps) => {// position 相關(guān)邏輯return ({position.left});});
因此在?React hooks?組件中盡量不要寫流水線代碼,保持在 200 行左右最佳,通過組件化降低耦合和復(fù)雜度,還能優(yōu)化一定的性能。
場景2
class?對(duì)比?hooks,上代碼:
class Counter extends React.Component {state = {count: 0,};increment = () => {this.setState((prev) => ({count: prev.count + 1,}));};render() {const { count } = this.state;return; }}
function Counter() {const [count, setCount] = React.useState(0);function increment() {setCount((n) => n + 1);}return; }
憑直觀感受,你是否會(huì)覺得?hooks?等同于?class?的寫法?錯(cuò),hooks?的寫法已經(jīng)埋了一個(gè)坑。在?count?狀態(tài)更新的時(shí)候,?Counter?組件會(huì)重新執(zhí)行,這個(gè)時(shí)候會(huì)重新創(chuàng)建一個(gè)新的函數(shù)?increment。這樣傳遞給?ChildComponent?的?onClick?每次都是一個(gè)新的函數(shù),從而導(dǎo)致?ChildComponent?組件的?React.memo?失效。
解決辦法:
function usePersistFnany>(fn: T) { const ref = React.useRef(() => { throw new Error('Cannot call function while rendering.');});ref.current = fn;return React.useCallback(ref.current as T, [ref]);}
// 建議使用 `usePersistFn`const increment = usePersistFn(() => {setCount((n) => n + 1);});// 或者使用 useCallbackconst increment = React.useCallback(() => {setCount((n) => n + 1);}, []);
上面聲明了?usePersistFn?自定義?hook,可以保證函數(shù)地址在本組件中永遠(yuǎn)不會(huì)變化。完美解決?useCallback?依賴值變化而重新生成新函數(shù)的問題,邏輯量大的組件強(qiáng)烈建議使用。
不僅僅是函數(shù),比如每次?render?所創(chuàng)建的新對(duì)象,傳遞給子組件都會(huì)有此類問題。盡量不在組件的參數(shù)上傳遞因?render?而創(chuàng)建的對(duì)象,比如?style={{ width: 0 }}?此類的代碼用?React.useMemo?或?React.memo?編寫?equal?函數(shù)來優(yōu)化。
style?若不需改變,可以提取到組件外面聲明。盡管這樣做寫法感覺太繁瑣,但是不依賴?React.memo?重新實(shí)現(xiàn)的情況下,是優(yōu)化性能的有效手段。
const style: React.CSSProperties = { width: 100 };function CustomComponent() {return; }
場景3
對(duì)于復(fù)雜的場景,使用?useWhyDidYouUpdate?hook 來調(diào)試當(dāng)前的可變變量引起的?rerender。這個(gè)函數(shù)也可直接使用?ahooks?中的實(shí)現(xiàn)。
function useWhyDidYouUpdate(name, props) {const previousProps = useRef();useEffect(() => {if (previousProps.current) {const allKeys = Object.keys({ ...previousProps.current, ...props });const changesObj = {};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;});}const Counter = React.memo(props => {useWhyDidYouUpdate('Counter', props);return{props.count};});
當(dāng)?useWhyDidYouUpdate?中所監(jiān)聽的 props 發(fā)生了變化,則會(huì)打印對(duì)應(yīng)的值對(duì)比,是調(diào)試中的神器,極力推薦。
場景4
借助 Chrome Performance 代碼進(jìn)行調(diào)試,錄制一段操作,在?Timings?選項(xiàng)卡中分析耗時(shí)最長邏輯在什么地方,會(huì)展現(xiàn)出組件的層級(jí)棧,然后精準(zhǔn)優(yōu)化。

場景5
在?React?中是極力推薦函數(shù)式編程,可以讓數(shù)據(jù)不可變性作為我們優(yōu)化的手段。我在?React?class 時(shí)代大量使用了?immutable.js?結(jié)合?redux?來搭建業(yè)務(wù),與?React?中?PureComponnet?完美配合,性能保持非常好。但是在?React hooks?中再結(jié)合?typescript?它就顯得有點(diǎn)格格不入了,類型支持得不是很完美。這里可以嘗試一下?immer.js,引入成本小,寫法也簡潔了不少。
const nextState = produce(currentState, (draft) => {draft.p.x.push(2);})// truecurrentState === nextState;
場景6
復(fù)雜場景使用?Map?對(duì)象代替數(shù)組操作,map.get(),?map.has(),與數(shù)組查找相比尤其高效。
// Mapconst map = new Map([['a', { id: 'a' }], ['b', { id: 'b' }], ['c', { id: 'c' }]]);// 查找值map.has('a');// 獲取值map.get('a');// 遍歷map.forEach(n => n);// 它可以很容易轉(zhuǎn)換為數(shù)組Array.from(map.values());// 數(shù)組const list = [{ id: 'a' }, { id: 'b' }, { id: 'c' }];// 查找值list.some(n => n.id === 'a');// 獲取值list.find(n => n.id === 'a');// 遍歷list.forEach(n => n);
結(jié)語
React 性能調(diào)優(yōu),除了阻止?rerender,還有與寫代碼的方式有關(guān)系。最后,我要推一下近期寫的?React?狀態(tài)管理庫 https://github.com/MinJieLiu/heo,也可以作為性能優(yōu)化的一個(gè)手段,希望大家從?redux?的繁瑣中解放出來,省下的時(shí)間用來享受生活??

回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~
點(diǎn)擊“閱讀原文”查看 120+ 篇原創(chuàng)文章
