v19.2Latest

在元件之間共享狀態

有時,你會希望兩個元件的狀態始終一起變化。要做到這一點,請從兩者中移除狀態,將其移至它們最近的共同父元件,然後透過 props 向下傳遞給它們。這被稱為狀態提升,並且是編寫 React 程式碼時最常做的事情之一。

您將學習
  • 如何透過狀態提升在元件之間共享狀態
  • 什麼是受控與非受控元件

透過範例提升狀態

在這個範例中,父元件Accordion渲染兩個獨立的Panel

  • Accordion
    • Panel
    • Panel

每個Panel元件都有一個布林值isActive狀態,決定其內容是否可見。

按下兩個面板的顯示按鈕:

請注意,按下一個面板的按鈕不會影響另一個面板——它們是獨立的。

圖表顯示一個包含三個元件的樹狀結構,一個標記為 Accordion 的父元件和兩個標記為 Panel 的子元件。兩個 Panel 元件都包含值為 false 的 isActive。圖表顯示一個包含三個元件的樹狀結構,一個標記為 Accordion 的父元件和兩個標記為 Panel 的子元件。兩個 Panel 元件都包含值為 false 的 isActive。

最初,每個PanelisActive狀態是false,所以它們都顯示為摺疊狀態

與前一個相同的圖表,其中第一個子 Panel 元件的 isActive 被高亮顯示,表示點擊後 isActive 值設為 true。第二個 Panel 元件仍包含值 false。與前一個相同的圖表,其中第一個子 Panel 元件的 isActive 被高亮顯示,表示點擊後 isActive 值設為 true。第二個 Panel 元件仍包含值 false。

點擊任一Panel的按鈕只會更新該PanelisActive狀態

但現在假設你想改變設計,使得在任何時候只有一個面板是展開的。按照這個設計,展開第二個面板應該會摺疊第一個面板。你會怎麼做?

要協調這兩個面板,你需要透過三個步驟將它們的狀態「提升」到父元件:

  1. 移除子元件中的狀態。
  2. 從共同父元件傳遞硬編碼的資料。
  3. 將狀態新增到共同父元件,並與事件處理器一起向下傳遞。

這將允許Accordion元件協調兩個Panel,並且一次只展開一個。

步驟 1:從子元件中移除狀態

你將把PanelisActive控制權交給其父元件。這意味著父元件將把isActive作為 prop 傳遞給Panel。首先,Panel元件中移除這一行:

取而代之,將isActive加入到Panel的屬性清單中:

現在,Panel的父元件可以透過控制isActive,方法是將其作為屬性向下傳遞。 相反地,Panel元件現在對於沒有控制權 的值 isActive——這現在取決於父元件!

步驟 2:從共同的父元件傳遞硬編碼資料

要提升狀態,你必須找到你想要協調的 兩個 子元件的最接近的共同父元件:

  • Accordion(最接近的共同父元件)
    • Panel
    • Panel

在這個例子中,它是Accordion元件。由於它在兩個面板之上並且可以控制它們的屬性,它將成為哪個面板目前處於活動狀態的「單一真實來源」。讓Accordion元件向兩個面板傳遞一個硬編碼的isActive值(例如,true):

嘗試編輯 Accordion元件中的硬編碼isActive 值,並查看螢幕上的結果。

步驟 3:為共同的父元件加入狀態

提升狀態通常會改變你儲存為狀態的內容的性質。

在這個情況下,一次應該只有一個面板處於活動狀態。這意味著Accordion 共同父元件需要追蹤 哪個面板是活動的。它可以使用一個數字作為活動布林值 的索引來作為狀態變數,而不是 Panel

activeIndex0時,第一個面板是活動的;當它為1時,則是第二個。

點擊任一 Panel中的「顯示」按鈕需要改變Accordion中的活動索引。一個Panel無法直接設定activeIndex 狀態,因為它定義在 Accordion內部。Accordion 元件需要 明確允許 Panel元件改變其狀態,方法是將事件處理函式作為屬性向下傳遞

現在,<button> 內部的 Panel 將使用 onShow屬性作為其點擊事件處理函式:

這樣就完成了狀態提升!將狀態移入共同的父元件讓你能夠協調兩個面板。使用活動索引而非兩個「是否顯示」標誌,確保了在給定時間只有一個面板處於活動狀態。而將事件處理函式向下傳遞給子元件,則允許子元件改變父元件的狀態。

圖表顯示一個由三個元件組成的樹狀結構,一個標記為 Accordion 的父元件和兩個標記為 Panel 的子元件。Accordion 包含一個 activeIndex 值為零,該值轉變為 isActive 值 true 傳遞給第一個 Panel,以及 isActive 值 false 傳遞給第二個 Panel。圖表顯示一個由三個元件組成的樹狀結構,一個標記為 Accordion 的父元件和兩個標記為 Panel 的子元件。Accordion 包含一個 activeIndex 值為零,該值轉變為 isActive 值 true 傳遞給第一個 Panel,以及 isActive 值 false 傳遞給第二個 Panel。

最初,AccordionactiveIndex0,所以第一個Panel 收到 isActive = true

與前圖相同的圖表,父元件 Accordion 的 activeIndex 值被高亮顯示,表示點擊後值更改為一。流向兩個子元件 Panel 的路徑也被高亮,傳遞給每個子元件的 isActive 值設定為相反:第一個 Panel 為 false,第二個為 true。與前圖相同的圖表,父元件 Accordion 的 activeIndex 值被高亮顯示,表示點擊後值更改為一。流向兩個子元件 Panel 的路徑也被高亮,傳遞給每個子元件的 isActive 值設定為相反:第一個 Panel 為 false,第二個為 true。

AccordionactiveIndex 狀態變更為 1時,第二個Panel則會收到isActive = true

Deep Dive
受控與非受控元件

每個狀態的單一真實來源

在 React 應用程式中,許多元件都會有自己的狀態。有些狀態可能「存在」於接近葉子元件(樹狀結構底部的元件)的地方,例如輸入框。其他狀態可能「存在」於應用程式的頂層附近。例如,即使是客戶端路由函式庫,通常也是透過將當前路由儲存在 React 狀態中,並透過 props 向下傳遞來實現的!

對於每個獨特的狀態片段,你將選擇「擁有」它的元件。 這個原則也被稱為擁有 「單一真實來源」。這並不意味著所有狀態都存在於一個地方——而是對於每個狀態片段,都有一個特定的 元件來保存該資訊。與其在元件之間複製共享狀態,不如將其 提升到它們共同的父元件,並向下傳遞給需要它的子元件。

你的應用程式會隨著你的工作而改變。在弄清楚每個狀態片段「存在」於何處的過程中,將狀態向下移動或向上移動是很常見的。這都是過程的一部分!

要透過更多元件來體驗這種感覺,請閱讀 React 思維。

總結

  • 當你想要協調兩個元件時,將它們的狀態移到它們的共同父元件。
  • 然後透過 props 從它們的共同父元件向下傳遞資訊。
  • 最後,將事件處理函式向下傳遞,以便子元件可以改變父元件的狀態。
  • 將元件視為「受控」(由 props 驅動)或「非受控」(由狀態驅動)是很有用的。

嘗試一些挑戰

Challenge 1 of 2:同步輸入框 #

這兩個輸入框是獨立的。讓它們保持同步:編輯一個輸入框應該用相同的文字更新另一個輸入框,反之亦然。