從一個PR窺探React未來開發(fā)方式
大家好,我是卡頌。
都說Hooks是React的未來,但Hooks的最佳實踐是什么呢?
關(guān)于這塊知識,官方文檔一點兒都沒提及。
所以在實際項目中,常會出現(xiàn)類似下面的問題:
- // ...
- useEffect(() => {
- fetchData(a, b).then(
- // ...
- )
- }, [a, b])
- //...
useEffect依賴了a b兩個狀態(tài),當(dāng)其中任意一個變化后會執(zhí)行fetchData請求數(shù)據(jù)。
當(dāng)應(yīng)用變得復(fù)雜,要追蹤a、b何時變化變得越來越難。
假以時日接口調(diào)整,fetchData還需要狀態(tài)c作為參數(shù)。那么追蹤狀態(tài)變化的難度又會進(jìn)一步提高。
最終會導(dǎo)致:
- 輕則無意義的fetchData多次調(diào)用
- 重則邏輯出現(xiàn)難以追查的bug
有朋友會說:你可以封裝自定義Hook啊。
這只是將問題隱藏的更深了......
如何解決這個問題
以上問題的本質(zhì)原因是:「副作用」實在太多,可以被當(dāng)作「副作用」的東西也實在太多。這導(dǎo)致useEffect被濫用。
所以,要解決濫用問題,就需要為不同類型「副作用」提供官方解決方案。
這樣,具體問題有了具體解決方案,才不會useEffect一把梭。
從一個PR看到變化
最近React有個很不起眼的PR[1]:
大體意思是:
在之前,當(dāng)你在一個已經(jīng)卸載的組件(unmounted)中調(diào)用setState會觸發(fā)一個warning,這個PR將移除這個warning。
舉個例子,以下代碼在組件mount時注冊handleChange:
- useEffect(() => {
- function handleChange() {
- setState(store.getState())
- }
- store.subscribe(handleChange)
- return () => store.unsubscribe(handleChange)
- }, [])
如果你忘記寫這行解綁代碼:
- return () => store.unsubscribe(handleChange)
那么組件卸載后handleChange也可能被調(diào)用,進(jìn)而調(diào)用setState。
這是潛在的內(nèi)存泄漏。
在之前的React中,這種行為會報warning。
那為什么要移除這種行為下的warning呢?
PR的背后
一方面,這個warning有一定概率誤判,比如「點擊按鈕提交表單」:
- async function handleSubmit() {
- setPending(true)
- await post('/someapi')
- setPending(false)
- }
點擊按鈕后調(diào)用setPending觸發(fā)loading圖標(biāo)顯示,接著發(fā)起post請求。
有可能請求返回前組件就卸載了,此時會調(diào)用:
- setPending(false)
并不會有內(nèi)存泄漏風(fēng)險,但是會報warning。
不過warning移除還有另一個更本質(zhì)的原因:
在第一個示例中,我們在useEffect中調(diào)用store.subscribe,這種行為可以歸類為:
在組件中訂閱外部源
什么是「外部源」呢?
任何「變化與否不受React控制的源」都是「外部源」。
比如:
- 各種第三方狀態(tài)管理庫
- 希望location.hash變化觸發(fā)組件更新
未來所有這類行為都會收斂到useMutableSource這個Hook中。
更多例子
再比如,對于I/O操作(比如「請求數(shù)據(jù)」)這種大家都會放在useEffect中的邏輯,未來使用resource結(jié)合Suspense可能是更好的選擇:
- const resource = fetchDetail();
- function Page() {
- return (
- <Suspense fallback={<h1>Loading...</h1>}>
- <Details/>
- </Suspense>
- );
- }
- function Details() {
- const data = resource.read();
- return <h1>{data.name}</h1>;
- }
以上例子中,調(diào)用fetchDetail會發(fā)起數(shù)據(jù)請求。
Details組件調(diào)用resource.read直接消費數(shù)據(jù)即可。
如果數(shù)據(jù)還未返回,視圖會渲染最近的Suspense的fallback(即<h1>Loading...</h1>)。
總結(jié)
「副作用」是多種多樣的,以前沒得選,只能用useEffect。
隨著React18的穩(wěn)定,面對不同「副作用」場景,會有更明確的解決方案。
屆時,可能才最終迎來Hooks真香的時代......