[React] useLayoutEffect
  最近在工作時,剛好在研究一些渲染尺寸相關的問題,就聽說過 useLayoutEffect 這個 hook, 
然後最近在幫 senior 審 PR 時,看完後覺得可以用這 hook 解決 code 上面的一些問題,
介紹給 senior 後,他說很有幫助,讓我覺得很感動🥺🥺,我也能做出一點貢獻,因此決定來紀念一下
遇到的情境 & 問題
情境
想像當我們在玩需要闖關的遊戲,當我們在關卡中,
遊戲會幫我們保存紀錄,再次登入時,遊戲會幫我們進入到上次進度,
當我們上次已經闖關成功,再次登入會帶我們到關卡選擇畫面,
這時,我們設計登入遊戲的程式碼時,需要:
- user 登入時,嘗試取得上次遊戲紀錄,並顯示載入畫面
 - 成功取得紀錄後,導到現在的遊戲進度頁面
 - 沒有紀錄時,顯示主畫面
 
程式碼如下:
// mock get game level progress api
const getGameLevelProgressSuccess = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve()
    }, 3000);
  });
}
const Game = function Game() {
  const router = useRouter(); // we use next/router to redirect
  const [isLoading, setIsLoading] = useState(false);
  const enterGameLevelProgress = async () => {
    setIsLoading(true);
    try {
      await getGameLevelProgressSuccess();
      // because next router.replace is a promise,
      // we should await it to avoid redirect be called after finally block
      // and it will show a blink of Game level map content
      await router.replace('/level5');
    } catch (err) {
      // do nothing for now
    } finally {
      setIsLoading(false);
    }
  }
  useEffect(() => {
    // some synchronous calculation
    calFib(40);
    enterGameLevelProgress();
  }, []);
  return (
    <div className={styles.container}>
      {isLoading
        ? <div>Loading...</div>
        : <h1>This is all Game Level map</h1>
      }
    </div>
  );
};
上面程式碼結果如下:
問題
在開始載入畫面時,會先瞬間顯示 This is all Game Level map,才顯示 Loading 字樣,
看起來就像是閃頻一樣,這不是我們想要的效果!!!
這是因為 useEffect 會在 virtual DOM 渲染到畫面上後才觸發,
所以一開始會先顯示 isLoading === false 的狀態,
後續在 useEffect 啟動會才會改成 isLoading === true 的狀態
useLayoutEffect 怎麼解決問題
useLayoutEffect 會在渲染到 browser 前就先執行,就可以避免這個問題,如下圖
以下是改寫的程式碼:
// this will be executed before paint to the DOM
useLayoutEffect(() => {
  calFib(40);
  enterGameLevelProgress();
}, []);
執行的結果:
useLayoutEffect 的缺點
雖然這個 hook 有著非常棒的優點,可以讓我們在畫面渲染前就處理好資訊並同時渲染,
但是遇到大量的計算時,整個畫面就會卡住,
例如我們把 useLayoutEffect 的計算量加大
useLayoutEffect(() => {
  // massive calculation by 42 fibonacci number
  calFib(42);
  enterGameLevelProgress();
}, []);
畫面一開始就會卡住一陣子,計算完 calFib(42) 後才會開始進入 loading 畫面,
畫面如下:
此時我們的畫面會整個卡住,連按鈕都不能點擊了😱😱,
這也是為什麼 React 官方不推薦我們使用的原因 (useLayoutEffect)
程式碼
如果有興趣可以到線上編輯器來玩一下:https://stackblitz.com/edit/nextjs-y3ruoh?file=pages%2Findex.js,pages%2Flevel5.js,pages%2F_app.js
