【问题标题】:useState not updated as expecteduseState 未按预期更新
【发布时间】:2021-08-02 19:58:43
【问题描述】:

我有两个组件,一个放在另一个里面。子组件打印父组件传递的状态,并使用 useEffect 更新一次状态。在父组件中,我使用 setState(具有布尔值的对象)并将状态传递给所有子组件。

父组件:

export default function App() {
  const [state, setState] = useState({
    one: false,
    two: false,
    three: false
  });
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <ChildComponent
        value={state.one}
        setState={(val) => setState({ ...state, one: val })}
      />
      <ChildComponent
        value={state.two}
        setState={(val) => setState({ ...state, two: val })}
      />
      <ChildComponent
        value={state.three}
        setState={(val) => setState({ ...state, three: val })}
      />
    </div>
  );
}

子组件:

import { useEffect } from "react";

export default function ChildComponent({ value, setState }) {
  useEffect(() => {
    console.log("updated");
    setState(!value);
  }, []);
  return (
    <>
      <p>{value ? "true" : "false"}</p>
    </>
  );
}

这两个组件都非常简单,我希望看到所有布尔值都为真(因为子组件正在修改状态)。问题是只有状态的最后一项正在更新。有人可以解释我的代码有什么问题吗? A working example

【问题讨论】:

标签: reactjs


【解决方案1】:

状态更新取决于之前的状态值,您需要传递一个回调作为 setState 的参数。

您需要这样做:

setState={(val) => setState(prev => ({ ...prev, one: val }))}

在您当前的实现中,您传播的是状态的初始值,其中所有内容都是false,而不是状态的当前值。

示例: https://codesandbox.io/s/usestate-callback-1oq2n?file=/src/App.js

【讨论】:

  • 我已经尝试过了,但在我的示例中不起作用。再试一次,它工作大声笑。我想在代码前面的时间太多了。非常感谢!
【解决方案2】:

编辑:您可以按照@koralarts 的建议处理它,但如果您想了解它发生的原因,请阅读此内容。

让我们探索一下您的示例中发生了什么。

TLDR:您必须将 setState 调用拆分为父组件中每个子组件的单独挂钩,或者如果可能的话管理子组件中的状态。
每次在效果中调用 setState 时都会对 schedules 这个调用做出反应,而不是直接执行它。 您在同一个渲染中调用了 3 次相同的 setState 函数,并且每次 react 都会将调用安排到下一个渲染,因此会覆盖之前的计划。

如果可能,从子级管理此状态,如果没有,则为父级上的每个子级使用不同的 useState。

外行星:

让我们了解一个反应组件的不同生命周期:

  • 更新调用
  • 效果(useLayoutEffect 和后来的 useEffect)

你可以阅读完整的解释here(强烈推荐)。

参见this 沙盒(就像你的带有日志的)。

阅读“渲染”和“更新”之间的区别here

render - 根据当前状态创建 React 树,然后 树被传递给将更新 VDOM 的渲染器,并更改 将被刷新到浏览器的 DOM 中。渲染周期由几个 稍后将解释的阶段。当我们说一个组件' 渲染'我们通常谈论渲染周期的结束, 在将更改刷新到浏览器的 DOM 之后。

更新 - 当我们说组件“更新”时,我们说的是功能组件主体重新执行(可能使用不同的道具)。其中一个 渲染的阶段。多个更新周期是可能的 渲染周期。更新和渲染之间的区别示例 稍后。

现在阅读此important 注释:

从效果调用状态钩子(如 useEffect 或 useLayoutEffect)将 导致 React 安排另一个渲染。

从 FC 主体调用 state hook 将导致 React 安排另一个更新调用。

你的情况:

  • 更新阶段:父级
  • 更新阶段:儿童
  • 效果阶段:children => setState 调用导致响应调度父级的另一个渲染。这在同一阶段的每个孩子身上发生 3 次(useEffect),每个时间表都会覆盖前一个孩子的时间表。

【讨论】:

    【解决方案3】:

    我认为是因为setStateasynchronous 行为,当它呈现ChildComponent 时,它仍然有old state 而不是更新的。就像@koralarts 回答一样,您必须使用以前的状态来更新状态。

    【讨论】:

      【解决方案4】:

      看看这里发生了什么,当你在第一个子组件中发送 state 时,state.one 被更新为 true ,但是,你移动了转到第二个组件,即 使用与 state.one 为 false 的相同先前状态,并且在第三种情况下也会发生同样的情况。

      所以你需要做的是将新更新状态的回调传递给函数 setState。

      setState= {(val)=>{setState(currState => ({ ...currState, two: val })}}
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-22
        • 2018-08-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多