为什么需要具有功能更新表单的 React useState?

IT技术 reactjs react-hooks
2021-04-18 04:21:15

我正在阅读有关功能更新的React Hook 文档并查看此引用:

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

但是我看不出需要功能更新的目的是什么,它们与在计算新状态时直接使用旧状态有什么区别。

为什么 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就会调用新的闭包 for ,捕获最新的count.

4个回答

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>
    </>
  );
}
@AlexGessen,假设我快速点击 + 两次。初始值为 0。计数应增加到 2,即 0 -> 1 然后 1 -> 2。如果没有功能更新程序形式,则有可能在执行第二个 setCount(count + 1) 时,计数尚未更新为 1,因此它使用 count - 0 的旧值将 count 设置为 0 + 1。在类似的情况下,但使用功能更新程序,这是否意味着当第二个 setCount => prevCount + 1)),这里的 prevCount 会被更新,即使 count 本身仍然可能没有更新?
2021-05-23 04:21:15
是的,我想我必须同意这种可能的情况
2021-06-02 04:21:15
这是一个不同的案例。update 函数被认为是异步的,这意味着您不应期望在 setClount 调用后立即更改计数值,因此不能依赖该新值。我明白了,但这与我的用例不同。用新的状态替换一次有什么问题,这是基于旧的(大多数人都这样做)
2021-06-07 04:21:15
这正是原因 - 用户可以在重新渲染组件之前单击 + 和 - 或者非常快速地单击 + 两次,并且在第二次调用中的值count将是错误的(未更新)。
2021-06-17 04:21:15

我最近偶然发现了这个需求。例如,假设您有一个组件,该组件用一定数量的元素填充数组,并且能够根据某些用户操作附加到该数组(例如在我的情况下,我以用户身份一次加载 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
这是我需要的答案...正在处理合并数组
2021-06-06 04:21:15

我已经回答了一个类似的问题,它已关闭,因为这是规范问题 - 我不知道,在查看答案后,我决定在此处重新发布我的答案,因为我认为它增加了一些value。

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

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

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

1-异步操作(在这个例子中点击慢添加,然后在添加按钮上多次点击,你稍后会看到当点击慢添加按钮时状态被重置为闭包内部的状态)

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>
   
    </>
  );
}

另一种使用情况,使用功能的更新用setState-requestAnimationFrame与react钩。此处提供详细信息 - https://css-tricks.com/using-requestanimationframe-with-react-hooks/

总之,处理程序requestAnimationFrame被调用经常造成不正确的count值,当你这样做setCount(count+delta)另一方面,使用会setCount(prevCount => prevCount + delta)产生正确的值。