簡體   English   中英

我可以在 useEffect 掛鈎中設置狀態嗎

[英]Can I set state inside a useEffect hook

假設我有一些狀態依賴於其他一些狀態(例如,當 A 改變時我希望 B 改變)。

創建一個鈎子來觀察 A 並在 useEffect 鈎子中設置 B 是否合適?

效果是否會級聯,當我單擊按鈕時,第一個效果會觸發,導致 b 發生變化,導致第二個效果在下一次渲染之前觸發? 像這樣構造代碼有任何性能上的缺點嗎?

let MyComponent = props => {
  let [a, setA] = useState(1)
  let [b, setB] = useState(2)
  useEffect(
    () => {
      if (/*some stuff is true*/) {
        setB(3)
      }
    },
    [a],
  )
  useEffect(
    () => {
      // do some stuff
    },
    [b],
  )

  return (
    <button
      onClick={() => {
        setA(5)
      }}
    >
      click me
    </button>
  )
}

一般來說,在useEffect中使用setState會創建一個您很可能不希望導致的無限循環。 該規則有幾個例外情況,我將在稍后討論。

useEffect在每次渲染后調用,當在其中使用setState時,它將導致組件重新渲染,這將調用useEffect等等。

useEffect中使用useState不會導致無限循環的常見情況之一是,當您將空數組作為第二個參數傳遞給useEffect ,例如useEffect(() => {....}, [])這意味着效果函數應該被調用一次:僅在第一次安裝/渲染之后。 當您在組件中獲取數據並且希望將請求數據保存在組件的狀態中時,這會被廣泛使用。

為了將來的目的,這也可能有幫助:

useEffect中使用 setState 是可以的,您只需要注意已經描述的不要創建循環。

但這不是唯一可能發生的問題。 見下文:

想象一下,您有一個組件Comp從父級接收props ,並且根據props更改您想要設置Comp的狀態。 出於某種原因,您需要在不同的useEffect中為每個道具進行更改:

不要這樣做

useEffect(() => {
  setState({ ...state, a: props.a });
}, [props.a]);

useEffect(() => {
  setState({ ...state, b: props.b });
}, [props.b]);

它可能永遠不會改變 a 的狀態,如您在此示例中所見: https ://codesandbox.io/s/confident-lederberg-dtx7w

在這個例子中發生這種情況的原因是因為當您同時更改prop.aprop.b時,兩個 useEffects 在同一個反應周期中運行,所以當您執行setState{...state}的值在兩個useEffect中完全相同因為他們在同一個上下文中。 當您運行第二個setState時,它將替換第一個setState

這樣做

這個問題的解決方案基本上是像這樣調用setState

useEffect(() => {
  setState(state => ({ ...state, a: props.a }));
}, [props.a]);

useEffect(() => {
  setState(state => ({ ...state, b: props.b }));
}, [props.b]);

在此處查看解決方案: https ://codesandbox.io/s/mutable-surf-nynlx

現在,當您繼續執行setState時,您總是會收到最新和正確的狀態值。

效果總是在渲染階段完成后執行,即使您在一個效果中設置狀態,另一個效果將讀取更新的狀態並僅在渲染階段后對其執行操作。

話雖如此,最好以相同的效果采取兩種操作,除非b可能由於changing a以外的原因而發生更改,在這種情況下,您也希望執行相同的邏輯

useEffect可以掛鈎某個道具或狀態。 所以,為了避免無限循環鈎子,你需要做的是綁定一些變量或狀態來生效

例如:

useEffect(myeffectCallback, [])

上面的效果只會在組件渲染后觸發。 這類似於componentDidMount生命周期

const [something, setSomething] = withState(0)
const [myState, setMyState] = withState(0)
useEffect(() => {
  setSomething(0)
}, myState)

上面的效果只會觸發我的狀態已經改變這類似於componentDidUpdate除了不是每個改變的狀態都會觸發它。

您可以通過此鏈接閱讀更多詳細信息

▶ 1. 我可以在 useEffect 鈎子中設置狀態嗎?

原則上,您可以在需要的地方自由設置狀態——包括在useEffect內部, 甚至在渲染期間 只需確保通過正確設置 Hook deps和/或有條件地聲明來避免無限循環。


▶ 2. 假設我有一些狀態依賴於其他狀態。 創建一個鈎子來觀察 A 並在 useEffect 鈎子中設置 B 是否合適?

您剛剛描述useReducer的經典用例:

當您有涉及多個子值復雜狀態邏輯或下一個狀態依賴於前一個狀態時, useReducer通常比useState更可取。 反應文檔

當設置狀態變量取決於另一個狀態變量的當前值時,您可能想嘗試用useReducer替換它們。 [...] 當您發現自己正在編寫setSomething(something =>...)時,是時候考慮改用 reducer 了。 丹阿布拉莫夫,反應過度的博客

 let MyComponent = () => { let [state, dispatch] = useReducer(reducer, { a: 1, b: 2 }); useEffect(() => { console.log("Some effect with B"); }, [state.b]); return ( <div> <p>A: {state.a}, B: {state.b}</p> <button onClick={() => dispatch({ type: "SET_A", payload: 5 })}> Set A to 5 and Check B </button> <button onClick={() => dispatch({ type: "INCREMENT_B" })}> Increment B </button> </div> ); }; // B depends on A. If B >= A, then reset B to 1. function reducer(state, { type, payload }) { const someCondition = state.b >= state.a; if (type === "SET_A") return someCondition? { a: payload, b: 1 }: {...state, a: payload }; else if (type === "INCREMENT_B") return {...state, b: state.b + 1 }; return state; } ReactDOM.render(<MyComponent />, document.getElementById("root"));
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script> <div id="root"></div> <script>var { useReducer, useEffect } = React</script>


▶ 3. 效果會級聯嗎,當我點擊按鈕時,第一個效果會觸發,導致 b 改變,導致第二個效果在下一次渲染之前觸發?

useEffect始終在提交渲染並應用 DOM 更改運行。 第一個效果觸發,改變b並導致重新渲染。 此渲染完成后,由於b更改,第二個效果將運行。

 let MyComponent = props => { console.log("render"); let [a, setA] = useState(1); let [b, setB] = useState(2); let isFirstRender = useRef(true); useEffect(() => { console.log("useEffect a, value:", a); if (isFirstRender.current) isFirstRender.current = false; else setB(3); return () => { console.log("unmount useEffect a, value:", a); }; }, [a]); useEffect(() => { console.log("useEffect b, value:", b); return () => { console.log("unmount useEffect b, value:", b); }; }, [b]); return ( <div> <p>a: {a}, b: {b}</p> <button onClick={() => { console.log("Clicked;"); setA(5); }} > click me </button> </div> ); }. ReactDOM,render(<MyComponent />. document;getElementById("root"));
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script> <div id="root"></div> <script>var { useReducer, useEffect, useState, useRef } = React</script>


▶ 4. 像這樣構造代碼是否有性能上的缺點?

是的。 通過將b的狀態變化包裝在a的單獨useEffect中,瀏覽器有一個額外的布局/繪制階段——這些效果可能對用戶可見。 如果你沒有辦法嘗試useReducer ,你可以直接改變b狀態和a

 let MyComponent = () => { console.log("render"); let [a, setA] = useState(1); let [b, setB] = useState(2); useEffect(() => { console.log("useEffect b, value:", b); return () => { console.log("unmount useEffect b, value:", b); }; }, [b]); const handleClick = () => { console.log("Clicked;"); setA(5)? b >= 5: setB(1); setB(b + 1); }: return ( <div> <p> a, {a}: b; {b} </p> <button onClick={handleClick}>click me</button> </div> ); }. ReactDOM,render(<MyComponent />. document;getElementById("root"));
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script> <div id="root"></div> <script>var { useReducer, useEffect, useState, useRef } = React</script>

嘗試將setState包裝在檢查狀態是否需要更改的 if 語句中 - 如果是,請更改它,否則return () => {}

例如,

useEffect(() => {
    if(a.currentCondition !== a.desiredCondition) {
        setA();
    }
    return cleanup;
}, [b])

暫無
暫無

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

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