熟悉 React 開發者們,不知道有多久沒有看過官方文件了呢?自從 React hooks 推出,function component 成為主流之後,我看文件的次數也大幅減少了。這代表 React 的 API 相當成熟且容易上手,是好事一樁。不過有在密切追蹤 React 的開發者應該會知道,在幾年前 React 就開始籌備 beta 版的官方文件版(beta.reactjs.org),終於在 2023 年年初左右正式上線。連結
除了基礎教學之外,我覺得最值得一看的地方在最後一章節 – Escape Hatches,提到了很多在寫 React 會遇到的問題、常見的 anti-pattern 跟解決方法。
如果能仔細看完這個章節,理解並實際應用的話,大概也不需要去報名 React 線上課程了。
這個章節描述了很多什麼是 effect
,並且介紹如何正確使用 useEffect
。如同官方文件所說的,side effect 是一種讓外部的事件、狀態與 React 的世界同步的方法。只有在這種時候使用 useEffect
最自然,也最不容易產生非預期的結果。在 React 開發模式當中,甚至會故意執行 useEffect
兩次來幫助你 debug。
useEffect
的用法錯誤useEffect
useEffect
都是在渲染到螢幕之後才會執行,因此不需要太擔心 useEffect
會影響重繪效能useEffect
時,盡可能加入 cleanup 函數來避免元件 unmount 後可能產生的記憶體洩漏。在這邊很重要的一點在於,不要把「useEffect 竟然會執行兩次當作是問題」,而是「要怎麼做才能讓 useEffect 就算執行兩次也沒有問題」。
在實際開發當中,我們應該避免使用 useEffect
。這個章節寫了蠻多範例,幾乎涵蓋了大部分的使用場景。看似要用 useEffect
,但其實完全可以避免的 pattern。在實際應用當中,每次寫到 useEffect
時,都可以想想是否有更好的寫法來避免 useEffect
。
例如要計算某個依賴與其他狀態或 prop 的變數,與其直接寫在 useEffect
,直接把計算搬出來即可,以官方的範例來說:
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// ✅ Good: calculated during rendering
const fullName = firstName + ' ' + lastName;
// ...
}
如果計算比較複雜一點,我喜歡直接用 useMemo
將計算包起來:
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
const fullName = useMemo(() => {
return firstName + ' ' + lastName;
}, [firstName, lastName]);
}
有時候想要在 userId
改變時重置狀態,這時候與其在 useEffect
寫重置的邏輯,直接在父元件裡加入 key={userId}
即可。 只要 React 偵測到 key 不同就會重新渲染。這樣就不用大費周章額外寫 useEffect
了。
一般來說,呼叫 API 有兩種大分類:
舉例來說,直接在 useEffect
裡頭呼叫 fetch:
useEffect(() => {
fetch('/api/xxx').then(...).then(data => setData(data))
...
}, [])
這樣子寫有幾個問題:
useEffect
一定要在 client 端執行比較好的方法是:
useSWR
等等以 React Query 來舉例,為了避免上述提到的問題,React Query 其實將整個 API fetch 的邏輯和 fetch 時的狀態管理都搬到 React 外部,並透過其他方式與 React 同步。這樣一來就不會被元件的生命週期所影響。除此之外 React Query 也能和 SSR 的框架搭配,初始資料也可以直接用同樣的 API 介面來存取。
官方建議將 react-hooks/exhaustive-deps
開啟,比較容易找到問題,大部分的情況都是在依賴有變化時想要重新執行 effect。
useEffect
裡面儘可能只放跟依賴相關的程式碼從 prop 傳過來的函數,在沒有用 useCallback
包住的情況下,每次傳過來的函數都會不一樣,因此加在依賴列表裡面會有造成不必要的 effect 執行。
目前只能暫時將它移除,或是再另外包一層 useCallback
確保他不會變化。最近 React 推出了新的 hook – useEffectEvent
,方便在這種情況下大家可以直接用一個 hook 包起來,不需要額外放到依賴列表裡。
如果確定傳入的值不會有變化,直接將它放到元件外部以常數宣告也可以:
const options = {
timeout: 5000,
}
function Chat() {
useEffect(() => {
const connection = createConnection(options);
connection.connect()
return () => connection.disconnect()
}, [])
}
這樣子完全沒有問題,因為值不會有變化。
在 Escape Hatches 裏面每個章節都有一些小挑戰,可以的話盡可能完成它。我覺得裡面的題目很多都是在實際開發時會碰到的問題,全部都寫完的話很有幫助!
React 官方文件寫的很用心,除了認識到 React 的各種 pattern 之外,也有很多現代前端開發必須要注意的事情。**而且全部都是免費的!**由於篇幅的關係,我只挑一些我覺得蠻重要的地方來講,但是裡頭每個章節都很精彩,推薦給大家。