狀態如同快照
狀態變數看起來像是你可以讀寫的普通 JavaScript 變數。然而,狀態的行為更像是一個快照。設定它並不會改變你已有的狀態變數,而是會觸發重新渲染。
您將學習
- 設定狀態如何觸發重新渲染
- 狀態何時以及如何更新
- 為何設定狀態後狀態不會立即更新
- 事件處理器如何存取狀態的「快照」
設定狀態會觸發渲染
你可能會認為你的使用者介面會直接響應使用者事件(例如點擊)而改變。在 React 中,它的運作方式與這種心智模型略有不同。在前一頁,你看到設定狀態會請求重新渲染來自 React。這意味著,要讓介面對事件做出反應,你需要更新狀態。
在這個範例中,當你按下「傳送」時,setIsSent(true)會告訴 React 重新渲染 UI:
當你點擊按鈕時會發生以下事情:
- 執行
onSubmit事件處理器。 setIsSent(true)將isSent設定為true並排入新的渲染佇列。- React 根據新的
isSent值重新渲染元件。
讓我們更仔細地看看狀態與渲染之間的關係。
渲染會擷取時間點的快照
「渲染」意味著 React 正在呼叫你的元件,它是一個函式。你從該函式返回的 JSX 就像是 UI 在時間點上的快照。它的 props、事件處理器和區域變數都是使用渲染時的狀態計算出來的。
與照片或電影畫面不同,您返回的 UI「快照」是互動式的。它包含邏輯,例如指定如何回應輸入的事件處理器。React 會更新畫面以匹配此快照並連接事件處理器。因此,按下按鈕將觸發您 JSX 中的點擊處理器。
當 React 重新渲染一個元件時:
- React 再次呼叫您的函數。
- 您的函數返回一個新的 JSX 快照。
- 然後 React 更新畫面以匹配您的函數返回的快照。
React 執行函數
計算快照
更新 DOM 樹
作為元件的記憶體,狀態不像一個在函數返回後就消失的普通變數。狀態實際上「存在」於 React 本身——就像放在架子上!——在您的函數之外。當 React 呼叫您的元件時,它會為該次特定渲染提供一個狀態的快照。您的元件在其 JSX 中返回一個包含一組新的 props 和事件處理器的 UI 快照,所有這些都是使用該次渲染的狀態值計算出來的!
您告訴 React 更新狀態
React 更新狀態值
React 將狀態值的快照傳遞給元件
這裡有一個小實驗來展示這是如何運作的。在這個例子中,您可能預期點擊「+3」按鈕會將計數器增加三次,因為它呼叫了setNumber(number + 1)三次。
看看當您點擊「+3」按鈕時會發生什麼:
請注意,number每次點擊只會增加一次!
設定狀態只會改變下一次渲染的狀態。在第一次渲染時,number是0。這就是為什麼,在該次渲染的onClick處理函式中,number的值即使在呼叫了setNumber(number + 1)之後,仍然為0:
以下是這個按鈕的點擊處理函式告訴 React 要做的事情:
setNumber(number + 1):number是0,所以setNumber(0 + 1)。- React 準備在下一次渲染時將
number更改為1。
- React 準備在下一次渲染時將
setNumber(number + 1):number是0,所以setNumber(0 + 1)。- React 準備在下一次渲染時將
number更改為1。
- React 準備在下一次渲染時將
setNumber(number + 1):number是0,所以setNumber(0 + 1)。- React 準備在下一次渲染時將
number更改為1。
- React 準備在下一次渲染時將
即使你呼叫了setNumber(number + 1)三次,在這次渲染的事件處理函式中,number始終是0,所以你將狀態設為1三次。這就是為什麼,在你的事件處理函式完成後,React 會以number等於1而非3來重新渲染元件。
你也可以透過在腦海中將狀態變數替換為其在程式碼中的值來可視化這個過程。由於number狀態變數在0,其事件處理函式看起來像這樣:這次渲染中是
對於下一次渲染,number是1,所以該次渲染的點擊處理函式看起來像這樣:
這就是為什麼再次點擊按鈕會將計數器設為2,然後在下一次點擊時設為3,依此類推。
狀態隨時間變化
嗯,這很有趣。試著猜猜點擊這個按鈕會彈出什麼警告:
如果你使用之前提到的替換方法,你可以猜到警告框會顯示「0」:
但如果你為警告框加上計時器,讓它只在元件重新渲染之後才觸發呢?它會顯示「0」還是「5」?猜猜看!
感到驚訝嗎?如果你使用替換方法,你可以看到傳遞給警告框的狀態「快照」。
當警告框執行時,儲存在 React 中的狀態可能已經改變,但它是使用使用者與其互動時的狀態快照來排程的!
狀態變數的值在單次渲染中永遠不會改變,即使其事件處理常式的程式碼是非同步的。在該次渲染的onClick內部,number的值在呼叫0之後仍然是setNumber(number + 5)。當 React 透過呼叫你的元件來「拍攝」UI 的快照時,它的值就被「固定」了。
這裡有一個例子,說明這如何讓你的事件處理常式較不容易出現時序錯誤。下面是一個會在五秒延遲後發送訊息的表單。想像這個情境:
- 你按下「傳送」按鈕,向 Alice 傳送「Hello」。
- 在五秒延遲結束前,你將「收件人」欄位的值更改為「Bob」。
你預期alert會顯示什麼?它會顯示「You said Hello to Alice」嗎?還是會顯示「You said Hello to Bob」?根據你所知的知識猜猜看,然後試試看:
React 會將狀態值「固定」在單次渲染的事件處理常式中。你不需要擔心程式碼執行時狀態是否已改變。
但如果你想在重新渲染前讀取最新的狀態呢?你會需要使用狀態更新函式,這將在下一頁介紹!
總結
- 設定狀態會請求一次新的渲染。
- React 將狀態儲存在你的元件之外,就像放在架子上。
- 當你呼叫
useState時,React 會給你該次渲染的狀態快照。 - 變數和事件處理常式不會「存活」於重新渲染之間。每次渲染都有其自己的事件處理常式。
- 每次渲染(及其內部的函式)總是會「看到」React 提供給該次渲染的狀態快照。
- 你可以在心智上替換事件處理常式中的狀態,類似於你思考渲染出的 JSX 的方式。
- 過去建立的事件處理常式擁有它們被建立時的那次渲染的狀態值。
嘗試一些挑戰
Challenge 1 of 1:實作一個交通號誌 #
這是一個行人穿越道號誌組件,按下按鈕時會切換狀態:
在點擊處理函式中加入一個 alert。當號誌為綠色並顯示「Walk」時,點擊按鈕應顯示「Stop is next」。當號誌為紅色並顯示「Stop」時,點擊按鈕應顯示「Walk is next」。
將 alert 放在 setWalk 呼叫之前或之後,會有差別嗎?
