簡體   English   中英

為什么每次渲染都會調用來自 `useEffect` 的清理函數?

[英]Why is the cleanup function from `useEffect` called on every render?

我一直在學習 React 並且我讀到從useEffect返回的函數是為了進行清理,而 React 在組件卸載時執行清理。

因此,我對其進行了一些試驗,但在以下示例中發現,每次組件重新渲染時都會調用該函數,而不是僅在從 DOM 卸載時調用該函數,即console.log("unmount"); 每次組件重新渲染時。

這是為什么?

function Something({ setShow }) {
  const [array, setArray] = useState([]);
  const myRef = useRef(null);

  useEffect(() => {
    const id = setInterval(() => {
      setArray(array.concat("hello"));
    }, 3000);
    myRef.current = id;
    return () => {
      console.log("unmount");
      clearInterval(myRef.current);
    };
  }, [array]);

  const unmount = () => {
    setShow(false);
  };

  return (
    <div>
      {array.map((item, index) => {
        return (
          <p key={index}>
            {Array(index + 1)
              .fill(item)
              .join("")}
          </p>
        );
      })}
      <button onClick={() => unmount()}>close</button>
    </div>
  );
}

function App() {
  const [show, setShow] = useState(true);

  return show ? <Something setShow={setShow} /> : null;
}

實例: https : //codesandbox.io/s/vigilant-leavitt-z1jd2

當組件卸載時,React 執行清理。

我不確定你在哪里讀到的,但這個說法是不正確的。 當對該鈎子的依賴關系發生變化並且效果鈎子需要使用新值再次運行時,React 會執行清理工作 此行為是有意保持視圖對更改數據的反應性。 離開官方示例,假設一個應用程序從朋友的個人資料中訂閱狀態更新。 作為您的好朋友,您決定與他們成為朋友並與其他人成為朋友。 現在該應用程序需要取消訂閱前一個朋友的狀態更新並聽取新朋友的更新。 這很自然,也很容易通過useEffect工作方式實現。

 useEffect(() => { 
    chatAPI.subscribe(props.friend.id);

    return () => chatAPI.unsubscribe(props.friend.id);
  }, [ props.friend.id ])

通過在依賴列表中包含好友 id,我們可以指示只有在好友 id 更改時才需要運行鈎子。

在您的示例中,您已在依賴項列表中指定了array ,並且您正在以設定的時間間隔更改數組。 每次更改數組時,鈎子都會重新運行。

您可以通過從依賴項列表中刪除數組並使用setState鈎子的回調版本來實現正確的功能。 回調版本總是對上一版本的狀態進行操作,因此不需要每次數組變化時都刷新鈎子。

  useEffect(() => {
    const id = setInterval(() => setArray(array => [ ...array, "hello" ]), 3000);

    return () => {
      console.log("unmount");
      clearInterval(id);
    };
  }, []);

一些額外的反饋是直接在clearInterval使用 id,因為在您創建清理函數時關閉(捕獲)該值。 無需將其保存到 ref。

React 文檔對此有一個解釋部分

簡而言之,原因是這樣的設計可以防止陳舊數據和更新錯誤。

React 中的useEffect鈎子旨在處理初始渲染和任何后續渲染(這里有更多關於它的內容)。


效果是通過它們的依賴關系來控制的,而不是由使用它們的組件的生命周期來控制。

useEffect效果的依賴關系發生變化時, useEffect將清除之前的效果並運行新效果。

這樣的設計更容易預測——每個渲染都有自己獨立(純)的行為效果 這確保 UI 始終顯示正確的數據(因為 React 心智模型中的 UI 是特定渲染狀態的屏幕截圖)。

我們控制效果的方式是通過它們的依賴關系。

為了防止在每次渲染時運行清理,我們只需要不要更改效果的依賴項。

具體而言,在您的情況下,清理正在發生,因為array正在發生變化,即Object.is(oldArray, newArray) === false

useEffect(() => {
  // ...
}, [array]);
//  ^^^^^ you're changing the dependency of the effect

您通過以下行導致此更改:

useEffect(() => {
  const id = setInterval(() => {
    setArray(array.concat("hello")); // <-- changing the array changes the effect dep
  }, 3000);
  myRef.current = id;

  return () => {
    clearInterval(myRef.current);
  };
}, [array]); // <-- the array is the effect dep

查看代碼我可以猜到它是因為第二個參數[array] 您正在更新它,因此它將調用重新渲染。 嘗試設置一個空數組。

每次狀態更新都會調用重新渲染和卸載,並且該數組正在發生變化。

正如其他人所說,useEffect 取決於 useEffect 中第二個參數中指定的“array”的變化。 因此,通過將其設置為空數組,這將有助於在組件安裝時觸發一次 useEffect。

這里的技巧是改變數組的先前狀態。

setArray((arr) => arr.concat("hello"));

見下文:

  useEffect(() => {
     const id = setInterval(() => {
         setArray((arr) => arr.concat("hello"));
     }, 3000);
     myRef.current = id;
     return () => {
        console.log("unmount");
        clearInterval(myRef.current);
     };
  }, []);

我分叉了你的 CodeSandbox 進行演示: https ://codesandbox.io/s/heuristic-maxwell-gcuf7?file =/ src/index.js

似乎在意料之中。 根據此處的文檔,在首次渲染、每次更新和卸載后調用useEffect

https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

小費

如果您熟悉 React 類的生命周期方法,您可以將 useEffect Hook 視為 componentDidMount、componentDidUpdate 和之前的 componentWillUnmount 組合。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM