大家好,我卡頌。
一些同學喜歡在useEffect
中請求初始資料,類似這樣:
useEffect(() => {
fetch(xxx).then(data => setState(data.json()))
}, [])
但React18
並不推薦這種方式。
這麼寫有什麼問題?如果不推薦這種方式,那麼推薦的方式是什麼呢?
本文來看看Dan
在reddit是如何回答上述問題的。
歡迎加入人類高質量前端框架群,帶飛
這是一個普遍的問題
除了React
外,大部分以元件形式組織的前端框架,都有如下類似的API
:
effect
didMount
/didUpdate
如果有初始化時請求資料的需求,這類框架都會選擇在上述回撥函式內執行請求操作,並在資料返回後更新狀態。
所以,這並不是React
獨有的問題。相反,他很普遍。
之所以在React
中這麼突出,是因為React
官方在引導開發者不要用這種形式書寫程式碼(通過嚴格模式下useEffect執行兩次放大這個問題)。
而React
之所以這麼做,是為了專案的效能以及UX(User Experience,使用者體驗)。
下面我們來細聊這麼做的影響。注意,這些影響同樣適用於其他框架。
為什麼不推薦這麼寫?
需要解決競態問題
在useEffect
中請求資料要面臨的第一個問題是需要解決競態問題。
假設你有個元件User
,接收userID
作為props
,用userID
請求資料後展示使用者資訊。
下面是你的寫法:
function User({userID}) {
const [data, setData] = useState(null);
useEffect(() => {
const res = await fetch(`https://xxx/${userID}/`);
setData(res.json());
}, [userID]);
if (data) {
return <div>{data.name}</div>;
}
return null;
}
這裡有個開發階段很難復現的bug
—— 如果userID
變化足夠快,會發起多個不同的使用者請求。
而最終展示哪個使用者的資料,取決於哪個請求先返回。這就是請求的競態問題。
點選返回按鈕後重新請求資料
如果使用者跳轉到新的頁面後,又通過瀏覽器回退按鈕回到當前頁面,並不能立刻看到他跳轉前的頁面。
相反,看到的可能是個白屏 —— 因為還需要重新執行useEffect
獲取初始資料。
這個問題的本質原因是:沒有初始資料的快取。
CSR時的白屏時間
CSR
(Client-Side Rendering,客戶端渲染)時在useEffect
中請求資料,在資料返回前頁面都是白屏狀態。
瀑布問題
如果父子元件都依賴useEffect
獲取初始資料渲染,那麼整個渲染流程如下:
- 父元件
mount
- 父元件
useEffect
執行,請求資料 - 資料返回後重新渲染父元件
- 子元件
mount
- 子元件
useEffect
執行,請求資料 - 資料返回後重新渲染子元件
可見,當父元件資料請求成功後子元件甚至還沒開始首屏渲染。
這就是渲染中的瀑布問題 —— 資料像瀑布一樣一級一級向下流動,流到的元件才開始渲染,很低效。
既然直接寫useEffect
有這麼多問題,那麼推薦的方式是什麼呢?
推薦的方式
在Meta
公司內部,基於Relay
驅動資料(但請求資料要求使用GraphQL
),所以這套架構比較難在社群普及開。
但是,現在社群已經有了成熟的請求資料的方案。
對於SSR
,可以使用Next.js
、Remix
接管資料請求。
對於CSR
,可以使用React Query
、useSWR
接管資料請求。
這些成熟的方案都致力於解決上述提到的問題。
如果不想使用這些方案,想自己寫,可以參考React
新文件中下面兩篇文章:
想看中文的同學,可以看我寫的總結 —— React新文件:不要濫用effect哦
總結
本文我們聊了React18
之後不推薦的請求資料的方式以及推薦的請求資料的方式。
其中不推薦的請求資料的方式不僅存在於React
中,很多前端框架都有這樣的問題。