React新文檔:不要濫用Effect哦
大家好,我卡頌。
你或你的同事在使用??useEffect?
?時有沒有發生過以下場景:
當你希望??狀態a?
??變化后「發起請求」,于是你使用了??useEffect?
?:
useEffect(() => {
fetch(xxx);
}, [a])
這段代碼運行符合預期,上線后也沒問題。
隨著需求不斷迭代,其他地方也會修改??狀態a?
??。但是在那個需求中,并不需要??狀態a?
?改變后發起請求。
你不想動之前的代碼,又得修復這個??bug?
?,于是你增加了判斷條件:
useEffect(() => {
if (xxxx) {
fetch(xxx);
}
}, [a])
某一天,需求又變化了!現在請求還需要??b?
?字段。
這很簡單,你順手就將??b?
??作為??useEffect?
?的依賴加了進去:
useEffect(() => {
if (xxxx) {
fetch(xxx);
}
}, [a, b])
隨著時間推移,你逐漸發現:
- 「是否發送請求」與「if條件」相關。
- 「是否發送請求」還與「a、b等依賴項」相關。
- 「a、b等依賴項」又與「很多需求」相關。
根本分不清到底什么時候會發送請求,真是頭大...
如果以上場景似曾相識,那么??React?
?新文檔里已經明確提供了解決辦法。
一些理論知識
新文檔中這一節名為Synchronizing with Effects[1],當前還處于草稿狀態。
但是其中提到的一些概念,所有??React?
?開發者都應該清楚。
首先,??effect?
?這一節隸屬于Escape Hatches[2](逃生艙)這一章。
從命名就能看出,開發者并不一定需要使用??effect?
?,這僅僅是特殊情況下的逃生艙。
??React?
?中有兩個重要的概念:
- ?
?Rendering code?
?(渲染代碼)。 - ?
?Event handlers?
?(事件處理器)。
??Rendering code?
??指「開發者編寫的組件渲染邏輯」,最終會返回一段??JSX?
?。
比如,如下組件內部就是??Rendering code?
?:
function App() {
const [name, update] = useState('KaSong');
return <div>Hello {name}</div>;
}
??Rendering code?
?的特點是:他應該是「不帶副作用的純函數」。
如下??Rendering code?
??包含副作用(??count?
?變化),就是不推薦的寫法:
let count = 0;
function App() {
count++;
const [name, update] = useState('KaSong');
return <div>Hello {name}</div>;
}
處理副作用
??Event handlers?
??是「組件內部包含的函數」,用于執行用戶操作,可以包含??副作用?
?。
下面這些操作都屬于??Event handlers?
?:
- 更新?
?input?
?輸入框。 - 提交表單。
- 導航到其他頁面。
如下例子中組件內部的??changeName?
??方法就屬于??Event handlers?
?:
function App() {
const [name, update] = useState('KaSong');
const changeName = () => {
update('KaKaSong');
}
return <div onClick={changeName}>Hello {name}</div>;
}
但是,并不是所有副作用都能在??Event handlers?
?中解決。
比如,在一個聊天室中,「發送消息」是用戶觸發的,應該交給??Event handlers?
?處理。
除此之外,聊天室需要隨時保持和服務端的長連接,「保持長連接」的行為屬于副作用,但并不是用戶行為觸發的。
對于這種:在視圖渲染后觸發的副作用,就屬于??effect?
??,應該交給??useEffect?
?處理。
回到開篇的例子:
當你希望??狀態a?
?變化后「發起請求」,首先應該明確,你的需求是:
「狀態a變化,接下來需要發起請求」還是「某個用戶行為需要發起請求,請求依賴狀態a作為參數」?
如果是后者,這是用戶行為觸發的副作用,那么相關邏輯應該放在??Event handlers?
?中。
假設之前的代碼邏輯是:
- 點擊按鈕,觸發?
?狀態a?
?變化。 - ?
?useEffect?
?執行,發送請求。
應該修改為:
- 點擊按鈕,在事件回調中獲取?
?狀態a?
?的值。 - 在事件回調中發送請求。
經過這樣修改,「狀態a變化」與「發送請求」之間不再有因果關系,后續對??狀態a?
?的修改不會再有「無意間觸發請求」的顧慮。
總結
當我們編寫組件時,應該盡量將組件編寫為純函數。
對于組件中的副作用,首先應該明確:
是「用戶行為觸發的」還是「視圖渲染后主動觸發的」?
對于前者,將邏輯放在??Event handlers?
?中處理。
對于后者,使用??useEffect?
?處理。
這也是為什么??useEffect?
??所在章節在新文檔中叫做??Escape Hatches?
?? —— 大部分情況下,你不會用到??useEffect?
?,這只是其他情況都不適應時的逃生艙。
參考資料
[1]Synchronizing with Effects:https://beta-reactjs-org-git-effects-fbopensource.vercel.app/learn/synchronizing-with-effects。
[2]Escape Hatches:https://beta-reactjs-org-git-effects-fbopensource.vercel.app/learn/escape-hatches?。