排隊處理一系列狀態更新
設定狀態變數會將另一次渲染排入佇列。但有時您可能希望在排入下一次渲染之前,對該值執行多個操作。為此,了解 React 如何批次處理狀態更新會有所幫助。
您將學習
- 什麼是「批次處理」以及 React 如何使用它來處理多個狀態更新
- 如何連續對同一狀態變數套用多次更新
React 批次處理狀態更新
您可能預期點擊「+3」按鈕會將計數器遞增三次,因為它呼叫了三次setNumber(number + 1):
然而,正如您可能從前一節回憶起的,每次渲染的狀態值是固定的,因此第一次渲染的事件處理常式中的number值始終為0,無論您呼叫多少次setNumber(1):
但這裡還有另一個因素在起作用。React 會等到事件處理常式中的所有程式碼執行完畢後,才處理您的狀態更新。這就是為什麼重新渲染只會發生在這些之後所有setNumber()呼叫之後。
這可能會讓您想起餐廳服務生點餐的情景。服務生不會在您提到第一道菜時就跑去廚房!相反,他們會讓您完成點餐,讓您進行修改,甚至接受同桌其他人的點餐。

這讓您可以更新多個狀態變數——甚至來自多個元件——而不會觸發太多重新渲染。但這也意味著 UI 在您的事件處理常式及其中的任何程式碼完成之後才會更新。這種行為,也稱為批次處理,讓您的 React 應用程式執行得更快。它還避免了處理令人困惑的「半成品」渲染,即只有部分變數被更新的情況。
React 不會跨多個有意圖的事件(如點擊)進行批次處理——每次點擊都是單獨處理的。請放心,React 只會在通常安全的情況下進行批次處理。這確保了,例如,如果第一次點擊按鈕禁用了表單,第二次點擊不會再次提交它。
在下一次渲染前多次更新同一狀態
這是一個不常見的用例,但如果您想在下一次渲染前多次更新同一狀態變數,與其傳遞下一個狀態值,例如setNumber(number + 1),您可以傳遞一個函數,該函數根據佇列中的前一個狀態計算下一個狀態,例如setNumber(n => n + 1)。這是一種告訴 React「對狀態值執行某些操作」而不僅僅是替換它的方式。
現在嘗試遞增計數器:
在這裡,n => n + 1被稱為更新函數。當您將其傳遞給狀態設定函數時:
- React 會將此函數排入佇列,以便在事件處理常式中的所有其他程式碼執行完畢後進行處理。
- 在下一次渲染期間,React 會遍歷佇列並提供最終更新後的狀態。
以下是 React 在執行事件處理常式時處理這些程式碼行的方式:
setNumber(n => n + 1):n => n + 1是一個函數。React 將其加入佇列。setNumber(n => n + 1):n => n + 1是一個函數。React 將其加入佇列。setNumber(n => n + 1):n => n + 1是一個函數。React 將其加入佇列。
當你在下一次渲染期間呼叫useState 時,React 會遍歷佇列。先前的 number 狀態是 0,因此 React 將其作為 n參數傳遞給第一個更新函數。然後 React 將你前一個更新函數的返回值傳遞給下一個更新函數作為n,依此類推:
| 佇列中的更新 | n | 返回值 |
|---|---|---|
n => n + 1 | 0 | 0 + 1 = 1 |
n => n + 1 | 1 | 1 + 1 = 2 |
n => n + 1 | 2 | 2 + 1 = 3 |
React 將3 儲存為最終結果,並從 useState返回。
這就是為什麼在上面的例子中點擊「+3」會正確地將值增加 3。
如果在替換狀態後更新狀態會發生什麼
那麼這個事件處理函數呢?你認為下一次渲染時number 會是多少?
以下是這個事件處理函數告訴 React 要做的事情:
setNumber(number + 5):number是0,所以setNumber(0 + 5)。React 將「替換為5」加入其佇列。setNumber(n => n + 1):n => n + 1是一個更新函數。React 將該函數加入其佇列。
在下一次渲染期間,React 會遍歷狀態佇列:
| 已排隊的更新 | n | 回傳值 |
|---|---|---|
「替換為 5」 | 0(未使用) | 5 |
n => n + 1 | 5 | 5 + 1 = 6 |
React 將6 儲存為最終結果,並從 useState回傳。
注意
你可能已經注意到,setState(5) 實際上就像 setState(n => 5)一樣運作,但n並未被使用!
如果在更新狀態後替換它會發生什麼事
讓我們再試一個例子。你認為下一次渲染時number 會是多少?
以下是 React 在執行此事件處理器時,處理這些程式碼行的方式:
setNumber(number + 5):number是0,所以setNumber(0 + 5)。React 將「替換為5」加入其佇列。setNumber(n => n + 1):n => n + 1是一個更新函式。React 將該函式加入其佇列。setNumber(42):React 將「替換為42」加入其佇列。
在下一次渲染期間,React 會遍歷狀態佇列:
| 已排隊的更新 | n | 回傳值 |
|---|---|---|
”替換為 5” | 0(未使用) | 5 |
n => n + 1 | 5 | 5 + 1 = 6 |
”替換為 42” | 6(未使用) | 42 |
然後 React 將42 儲存為最終結果,並從 useState回傳它。
總結來說,你可以這樣理解傳遞給 setNumber狀態設定器的內容:
- 一個更新函數(例如
n => n + 1)會被加入佇列。 - 任何其他值(例如數字
5)會將「替換為5」加入佇列,忽略已排隊的內容。
事件處理器完成後,React 將觸發重新渲染。在重新渲染期間,React 會處理佇列。更新函數在渲染期間執行,因此更新函數必須是 純粹的並且只回傳結果。不要嘗試從它們內部設定狀態或執行其他副作用。在嚴格模式下,React 會執行每個更新函數兩次(但丟棄第二次結果)以幫助你找出錯誤。
命名慣例
通常會以對應狀態變數的首字母來命名更新函數的參數:
如果你偏好更詳細的程式碼,另一個常見的慣例是重複完整的狀態變數名稱,例如setEnabled(enabled => !enabled),或使用前綴如 setEnabled(prevEnabled => !prevEnabled)。
總結
- 設定狀態不會改變現有渲染中的變數,而是請求一個新的渲染。
- React 在事件處理器執行完畢後處理狀態更新。這稱為批次處理。
- 要在一個事件中多次更新某些狀態,你可以使用
setNumber(n => n + 1)更新函數。
嘗試一些挑戰
Challenge 1 of 2:修復請求計數器 #
您正在開發一個藝術品市場應用程式,允許使用者同時為一件藝術品提交多個訂單。每次使用者按下「購買」按鈕時,「待處理」計數器應該增加一。三秒後,「待處理」計數器應該減少,而「已完成」計數器應該增加。
然而,「待處理」計數器的行為不符合預期。當您按下「購買」時,它會減少到 -1(這是不可能的!)。如果您快速點擊兩次,兩個計數器的行為似乎都變得不可預測。
為什麼會發生這種情況?請修復這兩個計數器。
