javascript 我可以在useEffect钩子内设置状态吗

vxqlmq5t  于 2022-12-25  发布在  Java
关注(0)|答案(6)|浏览(206)

假设我有一个依赖于其他状态的状态(例如,当A改变时,我希望B改变)。
在useEffect钩子中创建一个观察A并设置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>
  )
}
q3qa4bjr

q3qa4bjr1#

一般来说,在useEffect中使用setState将创建一个无限循环,这很可能是您不希望造成的。对于该规则,有几个例外,我将在后面介绍。
useEffect在每次渲染之后被调用,并且当setState在其内部被使用时,它将导致组件重新渲染,这将调用useEffect等等。
useEffect中使用useState不会导致无限循环的一个常见情况是,当您将一个空数组作为第二个参数传递给useEffect(如useEffect(() => {....}, []))时,这意味着effect函数应该被调用一次:当你在一个组件中进行数据获取,并且你想要在组件的状态中保存请求数据时,这被广泛使用。

li9yvcax

li9yvcax2#

为了将来的目的,这也可能有所帮助:
useEffect中使用setState是可以的,您只需要注意前面描述的不要创建循环。

但这不是唯一可能发生的问题。请参见以下内容:

假设您有一个组件Comp,它从父组件接收props,并且根据props的更改,您希望设置Comp的状态。由于某些原因,您需要更改不同useEffect中的每个属性:

不要这样做

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

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

它可能永远不会更改的状态,如您在此示例中所见:https://codesandbox.io/s/confident-lederberg-dtx7w
在本例中发生这种情况的原因是,当您同时更改prop.aprop.b时,两个useEffects在同一个React周期中运行,因此,当您执行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时,您总是会收到状态的最新且正确的值。

6xfqseft

6xfqseft3#

效果总是在渲染阶段完成后执行,即使您在一个效果中设置了状态,另一个效果也将读取更新的状态,并仅在渲染阶段后对其执行操作。
我已经说过,最好以相同的效果执行这两个操作,除非b可能由于changing a以外的原因而更改,在这种情况下,您也希望执行相同的逻辑

kdfy810k

kdfy810k4#

useEffect可以在某个属性或状态上挂钩。因此,要避免无限循环挂钩,您需要做的事情是绑定一些变量或状态来实现
例如:

useEffect(myeffectCallback, [])

上述效果将激发只有一次这组件已rendered.这是类似于componentDidMount生命周期

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

以上效果只在我的状态改变时触发,类似于componentDidUpdate,但不是每个改变的状态都会触发。
您可以通过此链接阅读更多详细信息

ddrv8njm

ddrv8njm5#

▶ 1.可以在useEffect钩子内设置状态吗?
原则上,您可以在需要的地方自由设置状态-包括在useEffect内部,甚至在渲染过程中。只需确保通过正确设置Hook deps和/或有条件地设置状态来避免无限循环。
▶ 2.假设我有一个状态依赖于另一个状态,在useEffect钩子中创建一个钩子来观察A并设置B合适吗?
您刚才描述了useReducer的 * 经典 * 使用情形:
当您有涉及多个子值复杂状态逻辑时,或者当下一个状态依赖于前一个状态时,useReducer通常优于useState。(React docs)
当设置一个state变量依赖于另一个state**变量的当前值时,你可能想尝试用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更改,第二个效果将运行。
一个二个一个一个
▶ 4.这样的代码结构是否会降低性能?
是的。通过将b的状态更改 Package 到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>
gudnpqoy

gudnpqoy6#

尝试将setState Package 在一个if语句中,该语句检查是否需要更改状态-如果需要,则更改它,否则return () => {}
例如,

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

相关问题