[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