【问题标题】:Why React useState with functional update form is needed?为什么需要具有功能更新形式的 React useState?
【发布时间】:2020-01-09 16:58:05
【问题描述】:

我正在阅读 React Hook documentation 关于功能更新的内容,请参阅以下引文:

“+”和“-”按钮使用函数形式,因为更新 值基于之前的值

但我看不出需要功能更新的目的以及它们与直接使用旧状态计算新状态之间的区别。

为什么 React useState Hook 的更新函数需要函数式更新形式? 有哪些例子可以清楚地看到差异(所以使用直接更新会导致错误)?

例如,如果我从文档中更改此示例

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
    </>
  );
}

直接更新count

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(count - 1)}>-</button>
    </>
  );
}

我看不到任何行为差异,也无法想象计数不会更新(或不是最新的)的情况。因为每当计数发生变化时,都会调用 onClick 的新闭包,捕获最新的 count

【问题讨论】:

    标签: reactjs react-hooks


    【解决方案1】:

    状态更新在 React 中是异步的。因此,当您下次更新时,count 中可能会有旧值。例如,比较这两个代码示例的结果:

    function Counter({initialCount}) {
      const [count, setCount] = useState(initialCount);
      return (
        <>
          Count: {count}
          <button onClick={() => setCount(initialCount)}>Reset</button>
          <button onClick={() => {
            setCount(prevCount => prevCount + 1); 
            setCount(prevCount => prevCount + 1)}
          }>+</button>
        </>
      );
    }
    

    function Counter({initialCount}) {
      const [count, setCount] = useState(initialCount);
      return (
        <>
          Count: {count}
          <button onClick={() => setCount(initialCount)}>Reset</button>
          <button onClick={() => {
            setCount(count + 1); 
            setCount(count + 1)}
          }>+</button>
        </>
      );
    }
    

    【讨论】:

    • 这是另一种情况。 update 函数被认为是异步的,这意味着您不应期望在 setClount 调用后立即更改 count 的值,因此不能依赖该新值。我明白了,但这与我的用例不同。用新的替换状态一次有什么问题,它基于旧的(就像大多数人一样)
    • 这正是原因 - 用户可以在重新渲染组件之前单击 + 和 - 或者非常快速地单击 + 两次,而在第二次调用中 count 的值将是错误的(未更新)。
    • 是的,我想我必须同意这种可能的情况
    • @AlexGessen,假设我快速单击 + 两次。初始值为 0。计数应递增到 2,即 0 -> 1,然后 1 -> 2。如果没有功能更新程序形式,则有可能在执行第二个 setCount(count + 1) 时,计数尚未更新为 1,因此它使用 count - 0 的旧值将 count 设置为 0 + 1。在类似的情况下,但使用功能更新程序,这是否意味着当第二个 setCount 发生时(setCount(prevCount => prevCount + 1)),这里的prevCount会更新,即使count本身可能还没有更新?
    • 真正让我困惑的是,他们的钩子 hello world at:reactjs.org/docs/hooks-state.html 只是 onClick={() =&gt; setCount(count + 1)}。给错误的世界打招呼是邪恶的!
    【解决方案2】:

    我最近偶然发现了这个需求。例如,假设您有一个组件,它用一定数量的元素填充数组,并且能够根据某些用户操作附加到该数组(就像在我的情况下,我作为用户一次加载 10 个项目一直向下滚动屏幕。代码看起来像这样:

    function Stream() {
      const [feedItems, setFeedItems] = useState([]);
      const { fetching, error, data, run } = useQuery(SOME_QUERY, vars);
    
      useEffect(() => {
        if (data) {
          setFeedItems([...feedItems, ...data.items]);
        }
      }, [data]);     // <---- this breaks the rules of hooks, missing feedItems
    
    ...
    <button onClick={()=>run()}>get more</button>
    ...
    
    

    显然,您不能只将 feedItems 添加到 useEffect 挂钩中的依赖项列表中,因为您正在其中调用 setFeedItems,因此您会陷入循环。

    救援功能更新:

    useEffect(() => {
        if (data) {
          setFeedItems(prevItems => [...prevItems, ...data.items]);
        }
      }, [data]);     //  <--- all good now
    

    【讨论】:

    • 这是我需要的答案...正在处理合并数组
    【解决方案3】:

    我已经像这样回答了similar question,它已关闭,因为这是规范问题 - 我不知道,在查看答案后,我决定在这里重新发布我的答案,因为我认为它增加了一些价值。

    如果您的更新依赖于在状态中找到的先前值,那么您应该使用函数形式。如果您在这种情况下不使用函数形式,那么您的代码有时会中断。

    为什么会破裂以及何时破裂

    React 功能组件只是闭包,闭包中的状态值可能已过时 - 这意味着闭包内的值与该组件处于 React 状态的值不匹配,这可能发生在以下情况:

    1-异步操作(In this example点击慢添加,然后多次点击添加按钮,稍后你会看到状态被重置为点击慢添加按钮时闭包内的状态)

    const App = () => {
      const [counter, setCounter] = useState(0);
    
      return (
        <>
          <p>counter {counter} </p>
          <button
            onClick={() => {
              setCounter(counter + 1);
            }}
          >
            immediately add
          </button>
          <button
            onClick={() => {
              setTimeout(() => setCounter(counter + 1), 1000);
            }}
          >
            Add
          </button>
        </>
      );
    };
    

    2- 当你在同一个闭包中多次调用更新函数时

    const App = () => {
      const [counter, setCounter] = useState(0);
    
      return (
        <>
          <p>counter {counter} </p>
          <button
            onClick={() => {
              setCounter(counter + 1);
              setCounter(counter + 1);
            }}
          >
            Add twice
          </button>
       
        </>
      );
    }
    

    【讨论】:

      【解决方案4】:

      另一个使用 setState - requestAnimationFrame 和 react hooks 的功能更新的用例。详细信息可在此处获得 - https://css-tricks.com/using-requestanimationframe-with-react-hooks/

      总之,requestAnimationFrame 的处理程序被频繁调用,导致 count 值不正确,当您执行 setCount(count+delta) 时。另一方面,使用setCount(prevCount =&gt; prevCount + delta) 会产生正确的值。

      【讨论】:

        猜你喜欢
        • 2020-11-08
        • 1970-01-01
        • 1970-01-01
        • 2023-03-21
        • 2021-02-27
        • 1970-01-01
        • 1970-01-01
        • 2021-03-03
        • 1970-01-01
        相关资源
        最近更新 更多