iT邦幫忙

2023 iThome 鐵人賽

DAY 16
0
Modern Web

30天React練功坊-攻克常見實務/面試問題系列 第 16

30天React練功坊-攻克常見實務/面試問題 Day16: React.memo not working corretly with function as props

  • 分享至 

  • xImage
  •  
tags: ItIron2023 react

前言

真是沒完沒了是吧!昨天我們繼續看了一個不必要重複渲染的例子,了解到為什麼切context需要謹慎並再次的請出我們的React.memo。今天我們則會繼續歡迎這個老朋友,我不是江郎才盡,只是這玩意能帶出的錯誤實在是有夠多,馬上就開始吧!

本日題目

請你看一下這個codesandbox以及下方的截圖。

day16-demo-image

今天我們在我們的App組件中有個ExpensiveChildComponent組件(請假設它裡面有很多運算或邏輯造成渲染成本很高),其中它接受了來自App組件的setState函數作為props傳入,為了避免App組件的改動造成不必要的重新渲染,你將整個組件用React.memo包起來,但當點擊ExpensiveChildComponent內的按鈕呼叫setCount函數時卻仍造成整個子組件重新渲染,請觀察以下的程式碼,試著解釋並修復這個問題。

const ExpensiveChildComponent = memo(({ onClick }) => {
  // Plz assume this component is expensive somehow
  console.log("ChildComponent re-rendered");
  return <button onClick={onClick}>Click Me</button>;
});

export default function App() {
  const [count, setCount] = useState(0);
  const increment = () => setCount(count + 1);

  return (
    <div>
      <h1>React.memo not working corretly with function as props</h1>
      <h2>Count: {count}</h2>
      <ExpensiveChildComponent onClick={increment} />
    </div>
  );

解答與基本解釋

首先我必須要先做個解釋,今天的題目看起來相當的不實際,畢竟正常來說你不會就這樣把setCount用另一個函數包裝後作為props傳進去,你大可以直接把setCount當作props傳進去,不過這足以展示我想說的問題了,這邊就稍微包容我一下吧!

我想如果你是從第一天就跟到現在的人,那你也許不知道怎麼修復這個問題,但你多半已經可以說明原因是什麼了,我們一再強調幾個概念,其中之一就是每一次的render都會重跑組件內的程式碼,包含變數與函數的宣告,再加上我們昨天也再次強調過React.memo僅做淺比對,以物件來說就是看reference是否有變動,現在仔細看一下程式碼。

const increment = () => setCount(count + 1);

<ExpensiveChildComponent onClick={increment} />

既然每一次的render都是新宣告的函數,reference自然也會是全新的,如此一來React.memo自然就再次判不上用場了,那麼你應該有方向我們該怎麼做了,我們要確保該函數有著相同的reference,除非必要,否則我們並不重新宣告這個函數。這就是useCallback出場的時候了,你要做的事情非常簡單,只需要將increment函數利用useCallback包起來並傳入空的dependency array就行了!

export default function App() {
  const [count, setCount] = useState(0);
  const increment = useCallback(() => setCount(prev => prev + 1), []); // 修改這邊即可


  return (
    <div>
      <h1>React.memo not working corretly with function as props</h1>
      <h2>Count: {count}</h2>
      <ExpensiveChildComponent onClick={increment} />
    </div>
  );
}

是不是覺得這類的解決方式開始似曾相似了?那就表示你開始慢慢了解React是怎麼去處理這類的問題,這些方式基本的原理都相當類似,只是使用的對象有些不同罷了。

總結

我們今天又一次的看了一個不必要渲染的問題,這個例子雖然很不實務但其實相當有意思,側面顯示了如果你並不了解他是怎麼運做的,很多時候你所謂的優化其實都是在做白工或扯後腿,務必評估你是否真的需要做這樣的優化、你的優化方式是否真的有效,我們這幾天談的React.memo, useMemo, useCallback適用情境都相當類似,絕大多數時候你都不需要把他們扯進來,再次強調,重新渲染是react很重要的一部分,它並不是什麼未知的猛獸需要被完全排除,你只要排除那些真正會造成不正確行為的即可! 關於這幾個小夥伴我們就這樣暫時告一段落了,在我們進入一些簡單的面試題目之前我們還會再有幾個新的情境,敬請期待!

本文章同步發布於個人部落格,有興趣的朋友也可以來逛逛~!


上一篇
30天React練功坊-攻克常見實務/面試問題 Day15: Unintended Re-renders: The Pitfalls of useContext
下一篇
30天React練功坊-攻克常見實務/面試問題 Day17: useEffect to the rescue, or is it?
系列文
30天React練功坊-攻克常見實務/面試問題30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言