React useEffect:每個開發(fā)都應(yīng)該知道的4個技巧

我們經(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ù)組時)。

例如:如果varBvarA沒有關(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>
  );
}

如你所見,變量varBvarA任何一個更改都將觸發(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

再次以上面的示例為例。如果變量varBvarA完全獨立怎么辦?

在這種情況下,可以簡單地創(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ù)。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容