v19.2Latest

狀態:元件的記憶

元件經常需要因應互動而改變畫面上顯示的內容。在表單中輸入應該更新輸入欄位、點擊圖片輪播的「下一張」應該變更顯示的圖片、點擊「購買」應該將商品放入購物車。元件需要「記住」一些東西:當前的輸入值、當前的圖片、購物車。在 React 中,這種元件專屬的記憶被稱為狀態

您將學習
  • 如何使用 useStateHook 來新增狀態變數
  • useStateHook 會回傳哪一對值
  • 如何新增多個狀態變數
  • 為何狀態被稱為區域性的

當普通變數不夠用時

這是一個渲染雕塑圖片的元件。點擊「下一張」按鈕應該透過將 index變更為1,接著2,依此類推來顯示下一件雕塑。然而,這行不通(你可以試試看!):

事件處理函式handleClick 正在更新一個區域變數 index。但有兩件事阻止了這個變更被看見:

  1. 區域變數不會在渲染之間持續存在。當 React 第二次渲染這個元件時,它會從頭開始渲染——不會考慮區域變數的任何變更。
  2. 對區域變數的變更不會觸發渲染。React 不會意識到它需要用新的資料再次渲染元件。

要用新資料更新元件,需要發生兩件事:

  1. 保留渲染之間的資料。
  2. 觸發React 使用新資料渲染元件(重新渲染)。

useStateHook 正好提供了這兩樣東西:

  1. 一個狀態變數,用於在渲染之間保留資料。
  2. 一個狀態設定函式,用於更新變數並觸發 React 再次渲染元件。

新增一個狀態變數

要新增一個狀態變數,請在檔案頂部從 React 匯入useState

然後,將這一行:

替換為

index是一個狀態變數,而setIndex 是設定函式。

這裡的[] 語法稱為 陣列解構,它讓你可以從陣列中讀取值。由 useState回傳的陣列總是恰好有兩個項目。

它們在 handleClick中是如何協同工作的:

現在點擊「Next」按鈕會切換當前的雕塑:

認識你的第一個 Hook

在 React 中,useState以及任何其他以「use」開頭的函式都被稱為 Hook。

Hook是一種特殊的函式,僅在 React 正在渲染時可用(我們將在下一頁詳細說明)。它們讓你能夠「掛入」不同的 React 功能。

狀態只是這些功能之一,但你稍後會遇到其他的 Hook。

陷阱

Hook——以use開頭的函式——只能在你的元件頂層或你自己的 Hook中呼叫。你不能在條件、迴圈或其他巢狀函式內部呼叫 Hook。Hook 是函式,但將它們視為關於元件需求的無條件宣告會有所幫助。你在元件頂層「使用」React 功能,類似於你在檔案頂層「匯入」模組。

剖析useState

當你呼叫useState時,你是在告訴 React 你希望這個元件記住某些東西:

在這個例子中,你希望 React 記住index

注意

慣例是將這對變數命名為像const [something, setSomething]這樣。你可以隨意命名,但慣例有助於跨專案理解。

傳遞給useState的唯一參數是你的狀態變數的初始值。在這個例子中,index的初始值透過useState(0)設定為0

每次你的元件渲染時,useState會給你一個包含兩個值的陣列:

  1. 狀態變數 (index),其中儲存了你設定的值。
  2. 狀態設定函數 (setIndex),它可以更新狀態變數並觸發 React 重新渲染元件。

以下是實際運作過程:

  1. 你的元件首次渲染。由於你傳遞了0useState 作為 index的初始值,它將回傳[0, setIndex]。React 會記住0 是最新的狀態值。
  2. 你更新了狀態。當使用者點擊按鈕時,它會呼叫setIndex(index + 1)index0,所以實際上是setIndex(1)。這告訴 React 現在要記住index1,並觸發另一次渲染。
  3. 你的元件第二次渲染。React 仍然看到useState(0),但因為 React記得 你將 index設為1,所以它會回傳[1, setIndex]
  4. 依此類推!

為元件設定多個狀態變數

你可以在一個元件中擁有任意數量和任意類型的狀態變數。這個元件有兩個狀態變數:一個數字index和一個布林值showMore,當你點擊「顯示詳細資訊」時會切換後者:

如果狀態之間沒有關聯,擁有多個狀態變數是個好主意,就像這個例子中的indexshowMore。但如果你發現經常同時更改兩個狀態變數,將它們合併成一個可能會更容易。例如,如果你有一個包含許多欄位的表單,使用一個持有物件的單一狀態變數會比每個欄位都有一個狀態變數更方便。閱讀選擇狀態結構以獲取更多提示。

狀態是隔離且私有的

狀態是螢幕上元件實例的本地狀態。換句話說,如果你渲染同一個元件兩次,每個副本都將擁有完全隔離的狀態!更改其中一個不會影響另一個。

在這個例子中,之前的Gallery元件被渲染了兩次,其邏輯沒有改變。試著點擊每個畫廊內的按鈕。請注意它們的狀態是獨立的:

這就是狀態與你在模組頂部宣告的普通變數不同之處。狀態並非繫結於特定的函式呼叫或程式碼中的某個位置,而是螢幕上特定位置的「局部」變數。你渲染了兩個 <Gallery />元件,因此它們的狀態是分開儲存的。

同時請注意 Page 元件如何完全「不知道」 Gallery的狀態,甚至不知道它是否有狀態。與 props 不同,狀態對於宣告它的元件來說是完全私有的。 父元件無法改變它。這讓你可以為任何元件新增狀態或移除狀態,而不影響其他元件。

如果你想讓兩個畫廊的狀態保持同步該怎麼辦?在 React 中的正確做法是移除 子元件中的狀態,並將其新增到它們最近的共用父元件中。接下來的幾頁將重點介紹單一元件的狀態組織,但我們會在 中回到這個主題。元件間共享狀態

總結

  • 當元件需要在渲染之間「記住」某些資訊時,使用狀態變數。
  • 狀態變數透過呼叫 useStateHook 來宣告。
  • Hook 是以 use開頭的特殊函式。它們讓你能夠「掛入」像狀態這樣的 React 功能。
  • Hook 可能會讓你想起 import:它們需要無條件地被呼叫。呼叫 Hook(包括useState)僅在元件或另一個 Hook 的頂層有效。
  • useStateHook 回傳一對值:目前的狀態和更新它的函式。
  • 你可以擁有多個狀態變數。在內部,React 會根據它們的順序進行配對。
  • 狀態對元件是私有的。如果你在兩個地方渲染它,每個副本都會有自己的狀態。

Try out some challenges

When you press “Next” on the last sculpture, the code crashes. Fix the logic to prevent the crash. You may do this by adding extra logic to event handler or by disabling the button when the action is not possible.

After fixing the crash, add a “Previous” button that shows the previous sculpture. It shouldn’t crash on the first sculpture.