iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 2
0
Modern Web

ReactJS 疑難排解系列 第 2

ReactJS 疑難排解:寫出一個好的 Render Counter

說到計算 render 次數,大家可能一開始想到的便是下面這段扣

const RenderCounter = () => {
  const renderCountRef = useRef(0);
  return <>{++renderCountRef.current}</>;
};

簡單地利用 ref 更新不會觸發 re-render 的特性,顯示出目前到底進去了幾次 render function

But 這是錯的!!!

這個故事要說到昨天晚上,原本想說寫文章前還是用 create-react-app 測一下,上面那段扣會不會正常 work,結果在每次強制 update 時都會計算成 render 了兩次!

React.StrictMode

✨ create-react-app 貌似從 16.X 版就會自帶 React.StrictMode

React.StrictMode,這是一個會在 dev 環境下會幫你 invoke 兩次 render function 的 component,讓你發現你目前的扣會不會有不正常的 side effect ?

正因為如此,所以我們的 renderCountRef.current 才會被加了兩次,你可能會想說把 React.StrictMode 拔掉不就好了,但之後換成 Concurrent Mode,就真的連他會 invoke render function 幾次都不知道了。

讓 render function 沒有 side effect

也就是不要在 render phase 去修改 ref 的值(global)

const RenderCounter = () => {
  const renderCountRef = useRef(0);
  let renderCount = renderCountRef.current;
  
  useEffect(() => {
    renderCountRef.current = renderCount;
  });
  
  renderCount += 1;
  return <>{renderCount}</>;
};

利用 renderCount 這個 local variable 在每次 render function 下有 scope 的特性,也就是只要一開始的 renderCountRef.current 是正確的,他就也會是對的。

那要怎麼確保 renderCountRef.current 是正確的呢?

我們可以利用 useEffect 只會 invoke 在 commit phase 的特性,在每次 renderCount 確實有跑進去 render function 後,再把這個值 assign 回 ref 的 current value。

附錄

什麼是 commit phase? 什麼是 render phase?

可以參考 Dan Abramov 的這則 twitter


上一篇
ReactJS 疑難排解:使用 react-devtools
下一篇
ReactJS 疑難排解:為什麼按下取消,form 卻 submit 了
系列文
ReactJS 疑難排解8

1 則留言

0
maxam
iT邦新手 5 級 ‧ 2020-09-19 00:18:20

在同事 code review 後,發現可以寫得更加精簡

simpler version

const RenderCounter = () => {
  const renderCountRef = useRef(1);

  useEffect(() => {
    renderCountRef.current += 1;
  });

  return <>{renderCountRef.current}</>;
};

我要留言

立即登入留言