為什麼 useState 多次更新不生效?

azoux發表於2024-08-18

問題

在編寫 React 程式碼時,如果你希望多次更新狀態,可能會嘗試使用 handleClickWrong 中的方式。然而,你會發現實際效果並不如預期:count 只增加了 1,而不是 3。

const root = document.getElementById('root');

const App = () => {
  return (
    <div>
      <h1>Hello World</h1>
      <Counter />
    </div>
  );
};

const Counter = () => {
  const [count, setCount] = React.useState(0);

  const handleClickWrong = () => {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
  };

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={handleClickWrong}>Increment</button>
    </div>
  );
};

const rootElement = ReactDOM.createRoot(root);
rootElement.render(<App />);

為什麼會出現這種情況呢?要弄清楚這個問題,我們需要了解 React 如何處理狀態更新的細節。

React 內部狀態更新機制

React 使用了一個內部機制來處理狀態更新。我們可以透過以下原始碼片段瞭解這背後的實現:

// React 原始碼中的基本狀態處理器
function basicStateReducer(state, action) {
  // $FlowFixMe: Flow doesn't like mixed types
  return typeof action === 'function' ? action(state) : action;
}

在我們使用 handleClickWrong 的情況下,React 會按照以下方式處理狀態更新:

// 假設初始狀態為 0,我們傳遞的值是 1
newState = basicStateReducer(0, 1);

由於 basicStateReducer 接收到的 action 不是函式,它會直接返回這個值作為最新狀態。因此,newState 會被更新為 1。

回到 handleClickWrong 的例子:

const handleClickWrong = () => {
  setCount(count + 1); // = basicStateReducer(0, 1) => 1
  setCount(count + 1); // = basicStateReducer(0, 1) => 1
  setCount(count + 1); // = basicStateReducer(0, 1) => 1
};

由於 count 在當前 Counter 函式的作用域內,三次 setCount 呼叫的值都是 count + 1。因此,即使呼叫了多次 setCount,由於 React 只處理了最新的 count 值,結果每次都將狀態更新為 1。最終渲染時,count 依然只有 1。

解決方案

瞭解了原因後,我們可以改用函式形式的更新來解決這個問題。注意到 basicStateReducer 會檢查傳入的 action,如果是函式,React 會用當前內部儲存的狀態作為引數傳給這個函式。這樣,我們可以基於最新的狀態進行更新。

const handleClick = () => {
  setCount((prevCount) => prevCount + 1); // = basicStateReducer(0, (prevCount) => prevCount + 1) => 0 + 1 => 1
  setCount((prevCount) => prevCount + 1); // = basicStateReducer(1, (prevCount) => prevCount + 1) => 1 + 1 => 2
  setCount((prevCount) => prevCount + 1); // = basicStateReducer(2, (prevCount) => prevCount + 1) => 2 + 1 => 3
};

透過使用函式形式的更新,我們可以確保每次狀態更新都基於前一個狀態,從而實現預期的結果。

相關文章