Jeff的隨手筆記

學習當一個前端工程師

0%

『Day 19』 React 狀態提升與元件協作

前言

在寫這系列文章的過程中,漸漸體會到 React 每個概念間的環環相扣,今天想跟大家分享一個在實務開發中經常會用到的重要概念:「狀態提升(Lifting State Up)」。

這個概念初次接觸時可能會覺得有點抽象,但其實它就像是一個團隊裡的溝通機制。想像一下,如果團隊中每個人都自己做決定,沒有互相溝通,那麼最後的成果可能會不太協調。但如果我們有一個共同的主管來協調大家的工作,事情就會順暢許多。在 React 的世界裡,「狀態提升」就扮演著這樣的角色。

為什麼需要狀態提升?

讓我們用一個簡單的手風琴選單(Accordion)來理解這個概念。

假設我們有這樣的需求:一次只能展開一個面板,當點擊另一個面板時,原本開啟的要自動關閉。

最直覺的作法可能是讓每個面板(Panel)自己管理自己的狀態:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function Panel({ title, children }) {
const [isActive, setIsActive] = useState(false);

return (
<section className="panel">
<h3>{title}</h3>
{isActive ? (
<p>{children}</p>
) : (
<button onClick={() => setIsActive(true)}>
展開
</button>
)}
</section>
);
}

function Accordion() {
return (
<>
<Panel title="關於我們">
我們是一個充滿熱情的團隊...
</Panel>
<Panel title="服務項目">
我們提供以下服務...
</Panel>
</>
);
}

這樣寫看起來沒什麼問題,但實際使用時你會發現:因為每個 Panel 都各自管理自己的狀態,所以無法實現「只展開一個面板」的需求。就像每個人都自己做決定,沒有人能統籌全局一樣。

實作狀態提升

為了解決這個問題,我們需要把狀態提升到父元件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function Accordion() {
const [activeIndex, setActiveIndex] = useState(0);

return (
<>
<Panel
title="關於我們"
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}
>
我們是一個充滿熱情的團隊...
</Panel>
<Panel
title="服務項目"
isActive={activeIndex === 1}
onShow={() => setActiveIndex(1)}
>
我們提供以下服務...
</Panel>
</>
);
}

function Panel({ title, children, isActive, onShow }) {
return (
<section className="panel">
<h3>{title}</h3>
{isActive ? (
<p>{children}</p>
) : (
<button onClick={onShow}>
展開
</button>
)}
</section>
);
}

這樣的改動帶來了幾個重要的變化:

  1. Panel 元件變成了「受控元件」,它的行為完全由外部控制
  2. 所有面板共用同一個狀態,確保一次只能展開一個
  3. 資料流向變得更清晰,更容易追蹤和除錯

狀態該放在哪裡?

在實務開發中,經常會遇到「這個狀態該放在哪裡」的問題。其實可以用一個簡單的原則來判斷:

找出所有需要這個狀態的元件,然後找到它們最近的共同父元件,通常這個父元件就是放置狀態的最佳位置。

就像我們的範例中,兩個 Panel 都需要知道「現在是否該展開」,所以我們把狀態放在它們的父元件 Accordion 中。

總結

狀態提升不只是一個技術概念,更是一種讓元件之間能夠優雅協作的設計模式。透過將需要共享的狀態提升到合適的層級,我們可以:

  • 讓元件之間的互動變得可能
  • 維持單一資料來源
  • 讓應用程式的資料流向更清晰

當然,這不是說所有狀態都要往上提升。有些純局部的狀態(例如表單的暫時輸入值)放在當地管理反而更合適。關鍵是要為每個狀態找到最恰當的位置,讓整個應用程式的架構更加合理且易於維護。