【问题标题】:useState hook setter incorrectly overwrites stateuseState 钩子设置器错误地覆盖了状态
【发布时间】:2020-01-31 05:45:54
【问题描述】:

这是问题所在:我试图在单击按钮时调用 2 个函数。这两个函数都会更新状态(我正在使用 useState 挂钩)。第一个函数将 value1 正确更新为“new 1”,但在 1s (setTimeout) 后第二个函数触发,并将 value 2 更改为“new 2”但是!它将 value1 设置回“1”。为什么会这样? 提前致谢!

import React, { useState } from "react";

const Test = () => {
  const [state, setState] = useState({
    value1: "1",
    value2: "2"
  });

  const changeValue1 = () => {
    setState({ ...state, value1: "new 1" });
  };
  const changeValue2 = () => {
    setState({ ...state, value2: "new 2" });
  };

  return (
    <>
      <button
        onClick={() => {
          changeValue1();
          setTimeout(changeValue2, 1000);
        }}
      >
        CHANGE BOTH
      </button>
      <h1>{state.value1}</h1>
      <h1>{state.value2}</h1>
    </>
  );
};

export default Test;

【问题讨论】:

  • 你能在changeValue2开头记录状态吗?
  • 我强烈建议您将对象拆分为对useState 的两个单独调用,或者改用useReducer
  • 是的 - 第二个。只需使用两次调用 useState()
  • const [state, ...],然后在setter中引用它...它会一直使用相同的状态。
  • 最佳行动方案:使用 2 个单独的 useState 调用,每个“变量”调用一个。

标签: javascript reactjs react-hooks


【解决方案1】:

欢迎来到封闭地狱。之所以会出现此问题,是因为每当调用 setState 时,state 都会获得新的内存引用,但函数 changeValue1changeValue2 由于关闭,会保留旧的初始 state 引用。

一种解决方案,以确保来自changeValue1changeValue2setState 获得最新状态是使用callback(具有以前的状态作为参数):

import React, { useState } from "react";

const Test = () => {
  const [state, setState] = useState({
    value1: "1",
    value2: "2"
  });

  const changeValue1 = () => {
    setState((prevState) => ({ ...prevState, value1: "new 1" }));
  };
  const changeValue2 = () => {
    setState((prevState) => ({ ...prevState, value2: "new 2" }));
  };

  // ...
};

您可以在herehere 找到有关此关闭问题的更广泛讨论。

【讨论】:

  • 带有 useState 钩子的回调似乎是 undocumented 功能,你确定它有效吗?
  • @HMR 是的,它有效,并且记录在另一个页面上。看看这里的“功能更新”部分:reactjs.org/docs/hooks-reference.html(“如果新状态是使用以前的状态计算的,您可以将函数传递给 setState”)
  • @HMR 但是,老实说,他们本可以做得更好,让这一点更显眼。
  • @AlbertoTrindadeTavares 是的,我也在查看文档,找不到任何东西。非常感谢您的回答!
  • 您的第一个解决方案不仅仅是“简单的解决方案”,而是正确的方法。第二个只有在组件被设计为单例时才有效,即使那样我也不确定,因为状态每次都会变成一个新对象。
【解决方案2】:

changeValue2被调用时,初始状态被保持,所以状态返回初始状态,然后value2属性被写入。

在此之后下次调用changeValue2,它保持状态{value1: "1", value2: "new 2"},所以value1属性被覆盖。

setState 参数需要一个箭头函数。

const Test = () => {
  const [state, setState] = React.useState({
    value1: "1",
    value2: "2"
  });

  const changeValue1 = () => {
    setState(prev => ({ ...prev, value1: "new 1" }));
  };
  const changeValue2 = () => {
    setState(prev => ({ ...prev, value2: "new 2" }));
  };

  return (
    <React.Fragment>
      <button
        onClick={() => {
          changeValue1();
          setTimeout(changeValue2, 1000);
        }}
      >
        CHANGE BOTH
      </button>
      <h1>{state.value1}</h1>
      <h1>{state.value2}</h1>
    </React.Fragment>
  );
};

ReactDOM.render(<Test />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

【讨论】:

    【解决方案3】:

    你的函数应该是这样的:

    const changeValue1 = () => {
        setState((prevState) => ({ ...prevState, value1: "new 1" }));
    };
    const changeValue2 = () => {
        setState((prevState) => ({ ...prevState, value2: "new 2" }));
    };
    

    因此,您可以通过在触发操作时使用之前的状态来确保不会丢失当前状态中的任何现有属性。也因此您避免必须管理闭包。

    【讨论】:

      【解决方案4】:

      发生的情况是changeValue1changeValue2从创建它们的渲染中看到状态,所以当你的组件第一次渲染时这两个函数看到:

      state= {
        value1: "1",
        value2: "2"
      }
      

      当您点击按钮时,首先调用changeValue1,并按预期将状态更改为{value1: "new1", value2: "2"}

      现在,1 秒后,changeValue2 被调用,但这个函数仍然看到初始状态({value1; "1", value2: "2"}),所以当这个函数以这种方式更新状态时:

      setState({ ...state, value2: "new 2" });

      你最终会看到:{value1; "1", value2: "new2"}

      source

      【讨论】:

        猜你喜欢
        • 2020-11-30
        • 1970-01-01
        • 2020-04-29
        • 2019-08-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-08-20
        • 2020-05-19
        相关资源
        最近更新 更多