我們經(jīng)常維護(hù)一些組件,組件起初很簡單,但是逐漸會被狀態(tài)邏輯和副作用充斥。每個生命周期常常包含一些不相關(guān)的邏輯。例如,組件常常在 componentDidMount 和 componentDidUpdate 中獲取數(shù)據(jù)。但是,同一個 componentDidMount 中可能也包含很多其它的邏輯,如設(shè)置事件監(jiān)聽,而之后需在 componentWillUnmount 中清除。相互關(guān)聯(lián)且需要對照修改的代碼被進(jìn)行了拆分,而完全不相關(guān)的代碼卻在同一個方法中組合在一起。如此很容易產(chǎn)生 bug,并且導(dǎo)致邏輯不一致。
在多數(shù)情況下,不可能將組件拆分為更小的粒度,因為狀態(tài)邏輯無處不在。這也給測試帶來了一定挑戰(zhàn)。同時,這也是很多人將 React 與狀態(tài)管理庫結(jié)合使用的原因之一。但是,這往往會引入了很多抽象概念,需要你在不同的文件之間來回切換,使得復(fù)用變得更加困難。
為了解決這個問題,Hook 將組件中相互關(guān)聯(lián)的部分拆分成更小的函數(shù)(比如設(shè)置訂閱或請求數(shù)據(jù)),而并非強(qiáng)制按照生命周期劃分。你還可以使用 reducer 來管理組件的內(nèi)部狀態(tài),使其更加可預(yù)測。
怎么才能把Effect Hook 用好呢?
我們來談?wù)凴eact Hooks中的useEffects,將與你分享4個有用的技巧。使用useEffect時應(yīng)該記住這四個技巧。
翻譯文章來源:Medium
原標(biāo)題:React useEffect: 4 Tips Every Developer Should Know
翻譯開始
將useEffect用于一個單一功能
在React Hooks,可以有多個useEffect函數(shù)。這是一個很棒的功能,如果我們分析如何編寫簡潔的代碼,會發(fā)現(xiàn)函數(shù)應(yīng)該提供一個單一功能(就像一個句子僅傳達(dá)一個想法一樣)。
將useEffect拆分為簡短而甜美的單用途函數(shù)也可以防止意外執(zhí)行(使用依賴數(shù)組時)。
例如:如果varB和varA沒有關(guān)系,并且想根據(jù)useEffect構(gòu)建一個遞歸計數(shù)器(有setTimeout),那么編寫一些錯誤的代碼:
function App() {
const [varA, setVarA] = useState(0);
const [varB, setVarB] = useState(0);
// Don't do this!
useEffect(() => {
const timeoutA = setTimeout(() => setVarA(varA + 1), 1000);
const timeoutB = setTimeout(() => setVarB(varB + 2), 2000);
return () => {
clearTimeout(timeoutA);
clearTimeout(timeoutB);
};
}, [varA, varB]);
return (
<span>
Var A: {varA}, Var B: {varB}
</span>
);
}
如你所見,變量varB和varA任何一個更改都將觸發(fā)兩個變量的更新。這就是為什么hook無法正常工作的原因。
由于這只是一個簡單的示例,也許會覺得這很簡單,但是,在更長的函數(shù)中使用更多的代碼和變量,我保存你會忽略這一點。因此,拆分useEffect才是最正確的做法。
這個示例,應(yīng)該是下面這樣:
function App() {
const [varA, setVarA] = useState(0);
const [varB, setVarB] = useState(0);
// Correct way
useEffect(() => {
const timeout = setTimeout(() => setVarA(varA + 1), 1000);
return () => clearTimeout(timeout);
}, [varA]);
useEffect(() => {
const timeout = setTimeout(() => setVarB(varB + 2), 2000);
return () => clearTimeout(timeout);
}, [varB]);
return (
<span>
Var A: {varA}, Var B: {varB}
</span>
);
}
?注意:這個代碼僅用于示例目的,旨在幫助你輕松理解
useEffect的問題。通常,當(dāng)變量依賴先前的狀態(tài)時,建議的方法是改為使用setVarA(varA => varA + 1)代替。
盡量使用自定義的hooks
再次以上面的示例為例。如果變量varB和varA完全獨立怎么辦?
在這種情況下,可以簡單地創(chuàng)建一個自定義鉤子來隔離每個變量。這樣,可以準(zhǔn)確的了解哪個函數(shù)對哪個變量執(zhí)行的操作。
接下來,我們來構(gòu)建一些自定義hooks!
function App() {
const [varA, setVarA] = useVarA();
const [varB, setVarB] = useVarB();
return (
<span>
Var A: {varA}, Var B: {varB}
</span>
);
}
function useVarA() {
const [varA, setVarA] = useState(0);
useEffect(() => {
const timeout = setTimeout(() => setVarA(varA + 1), 1000);
return () => clearTimeout(timeout);
}, [varA]);
return [varA, setVarA];
}
function useVarB() {
const [varB, setVarB] = useState(0);
useEffect(() => {
const timeout = setTimeout(() => setVarB(varB + 2), 2000);
return () => clearTimeout(timeout);
}, [varB]);
return [varB, setVarB];
}
現(xiàn)在每個變量都有自己的hook.更易于維護(hù)和閱讀!
正確的方式加條件地運行useEffect
看下面示例,關(guān)于setTimeout的主題:
function App() {
const [varA, setVarA] = useState(0);
useEffect(() => {
const timeout = setTimeout(() => setVarA(varA + 1), 1000);
return () => clearTimeout(timeout);
}, [varA]);
return <span>Var A: {varA}</span>;
}
由于某種原因,希望將計數(shù)器的最大值限制為5。給出一種正確的方法和一種錯誤的方法。
首先看錯誤的方式:
function App() {
const [varA, setVarA] = useState(0);
// Don't do this!
useEffect(() => {
let timeout;
if (varA < 5) {
timeout = setTimeout(() => setVarA(varA + 1), 1000);
}
return () => clearTimeout(timeout);
}, [varA]);
return <span>Var A: {varA}</span>;
}
盡管這可行,但請記住只要varA改變cleatTimeout就會運行,而setTimeout是在滿足條件下運行的。
推薦方式:在有條件的情況下運行useEffect,在頭部添加一個條件返回,如下:
function App() {
const [varA, setVarA] = useState(0);
useEffect(() => {
if (varA >= 5) return;
const timeout = setTimeout(() => setVarA(varA + 1), 1000);
return () => clearTimeout(timeout);
}, [varA]);
return <span>Var A: {varA}</span>;
}
使用Material UI能看到很多這種用法(以及許多其他UI框架),并且可以確保沒有錯誤的運行useEffect。
在依賴數(shù)組中鍵入useEffect內(nèi)部的每個prop
如果使用ESLint,可能會看到來自ESLint的警告exhaustive-deps rule。
這很關(guān)鍵。當(dāng)你的應(yīng)用程序變得越來越大,每個useEffect中都會添加更多的依賴項(props)。為了跟蹤所有這些props并避免過時的關(guān)閉,應(yīng)該將每個依賴項到依賴數(shù)組中。(Here’s the official take on this subject)
繼續(xù),上面的setTimeout,假設(shè)只想運行一次setTimeout并添加到var中,就像前面的示例一樣。
你可能嘗試下面錯誤的方式:
function App() {
const [varA, setVarA] = useState(0);
useEffect(() => {
const timeout = setTimeout(() => setVarA(varA + 1), 1000);
return () => clearTimeout(timeout);
}, []); // Avoid this: varA is not in the dependency array!
return <span>Var A: {varA}</span>;
}
盡管這個滿足需求,但讓我們再花點時間思考一下,“如果你的代碼變得更大會怎么樣?”,或者,“如果想將上面的代碼進(jìn)行更改怎么辦?”
在這種情況下,需要將所有的變量都映射出來,因為可以更輕松地測試和檢測可能出現(xiàn)的問題(之前的props和closures)。
正確的方式應(yīng)該是:
function App() {
const [varA, setVarA] = useState(0);
useEffect(() => {
if (varA > 0) return;
const timeout = setTimeout(() => setVarA(varA + 1), 1000);
return () => clearTimeout(timeout);
}, [varA]); // Great, we have our dependency array correctly set
return <span>Var A: {varA}</span>;
}
翻譯完畢!
如果有什么疑問或建議請在評論里面回復(fù)。
