簡體   English   中英

用空對象反應 useState 會導致無限循環

[英]React useState with an empty object causes an infinite loop

將 React 鈎子與子組件一起使用,該子組件應該從父組件獲取初始狀態並在每次內部狀態更改時更新父組件。
我認為由於它始終是相同的引用,因此不應無限調用 child 的 useEffect。

如果孩子的初始狀態是一個空對象,我會得到一個無限循環。
如果孩子的初始狀態是從道具中獲取的,那么效果很好。

不確定是什么原因造成的。
您可以將子組件內的第一個 useState 更改為空對象,以啟動無限循環。

請查看下面的沙箱:
https://codesandbox.io/s/weird-initial-state-xi5iy?fontsize=14&hidenavigation=1&theme=dark
注意:我在沙箱中添加了一個計數器,以在運行 10 次后停止循環並且不會使瀏覽器崩潰。

import React, { useState, useEffect, useCallback } from "react";

const problematicInitialState = {};

/* CHILD COMPONENT */
const Child = ({ onChange, initialData }) => {
  const [data, setData] = useState(initialData); // if initialData is {} (a.k.a problematicInitialState const) we have an infinite loop

  useEffect(() => {
    setData(initialData);
  }, [initialData]);

  useEffect(() => {
    onChange(data);
  }, [data, onChange]);

  return <div>Counter is: {data.counter}</div>;
};

/* PARENT COMPONENT */
export default function App() {
  const [counterData, setCounterData] = useState({ counter: 4 });

  const onChildChange = useCallback(
    (data) => {
      setCounterData(data);
    },
    [setCounterData]
  );

  return (
    <div className="App">
      <Child onChange={onChildChange} initialData={counterData} />
    </div>
  );
}

如何將狀態放在父組件中,而讓子組件只引用傳遞給它的道具,而沒有任何自己的狀態?

 const Child = ({ counterData, setCounterData }) => { return ( <div> <div>Counter is: {counterData.counter}</div> <button onClick={() => setCounterData({ counter: counterData.counter + 1 })} >increment</button> </div> ); }; const App = () => { const [counterData, setCounterData] = React.useState({ counter: 4 }); return ( <div className="App"> <Child {...{ counterData, setCounterData }} /> </div> ); } ReactDOM.render(<App />, document.querySelector('.react'));
 <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <div class="react"></div>

問題是在 JS {} !== {}因為對象與原始值不同,是通過引用而不是值進行比較。

在你useEffect你正在比較 2 個對象,因為它們總是有不同的引用,在 JS 領域永遠不會相同,你的useEffect將觸發,設置新對象,你得到了一個無限循環。

您不應該像在 React 中使用類組件那樣使用鈎子,這意味着您應該這樣做

const [counter, setCounter] = useState(4);

這樣,您將原始值傳遞給您的子組件,並且useEffect將具有更可預測的行為。

此外,雖然這是一個測試用例,但您應該很少(閱讀:從不)嘗試將子狀態設置為父狀態。 您已經將數據從父組件傳遞給子組件,無需在子組件中創建冗余狀態,只需使用傳入的數據即可。

關於解決方案,我建議您不要在子組件中設置任何初始狀態(或將其設置為空對象{} )。 第一個useEffect將處理第一次更新。

const Child = ({ onChange, initialData }) => {
  const [data, setData] = useState({});

  useEffect(() => {
    setData(initialData);
  }, [initialData]);

  useEffect(() => {
    onChange(data);
  }, [data, onChange]);

  return <div>Counter is: {data.counter}</div>;
};

至於其他評論,我同意,而是將狀態從父母傳遞給孩子。

暫無
暫無

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

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