React 元件間通訊模式類型(Patterns)
在 React 中,元件之間需要共享資料和互相通訊。本篇介紹幾種常見的元件間通訊模式。
Props 向下傳遞
最基本的通訊方式:父元件透過 props 將資料傳給子元件。
function Parent() {
const user = { name: 'Mike', age: 25 }
return <Child user={user} />
}
function Child({ user }) {
return (
<p>
姓名:{user.name},年齡:{user.age}
</p>
)
}
回調函式向上傳遞
子元件可以透過呼叫父元件傳入的回調函式,將資料「向上」傳遞:
function Parent() {
const [message, setMessage] = useState('')
function handleMessage(msg) {
setMessage(msg)
}
return (
<div>
<p>收到的訊息:{message}</p>
<Child onSendMessage={handleMessage} />
</div>
)
}
function Child({ onSendMessage }) {
return <button onClick={() => onSendMessage('Hello from Child!')}>發送訊息給父元件</button>
}
狀態提升 (Lifting State Up)
當多個元件需要共享同一份資料時,應該將狀態「提升」到它們最近的共同父元件。
範例:溫度轉換器
假設我們有兩個輸入框,一個輸入攝氏溫度,一個輸入華氏溫度,它們需要保持同步。
import { useState } from 'react'
// 溫度轉換函式
function toCelsius(fahrenheit) {
return ((fahrenheit - 32) * 5) / 9
}
function toFahrenheit(celsius) {
return (celsius * 9) / 5 + 32
}
// 溫度輸入元件
function TemperatureInput({ scale, temperature, onTemperatureChange }) {
const scaleNames = { c: '攝氏', f: '華氏' }
return (
<fieldset>
<legend>輸入{scaleNames[scale]}溫度:</legend>
<input
type="number"
value={temperature}
onChange={(e) => onTemperatureChange(e.target.value)}
/>
</fieldset>
)
}
// 父元件:狀態在這裡
function Calculator() {
const [temperature, setTemperature] = useState('')
const [scale, setScale] = useState('c')
function handleCelsiusChange(value) {
setScale('c')
setTemperature(value)
}
function handleFahrenheitChange(value) {
setScale('f')
setTemperature(value)
}
// 計算兩種溫度
const celsius = scale === 'f' ? toCelsius(parseFloat(temperature)) : temperature
const fahrenheit = scale === 'c' ? toFahrenheit(parseFloat(temperature)) : temperature
return (
<div>
<TemperatureInput
scale="c"
temperature={isNaN(celsius) ? '' : celsius}
onTemperatureChange={handleCelsiusChange}
/>
<TemperatureInput
scale="f"
temperature={isNaN(fahrenheit) ? '' : fahrenheit}
onTemperatureChange={handleFahrenheitChange}
/>
{temperature && (
<p>
{celsius}°C = {fahrenheit}°F
</p>
)}
</div>
)
}
重點:
- 狀態(
temperature和scale)存在父元件 - 子元件透過 props 接收資料
- 子元件透過回調函式通知父元件更新
兄弟元件通訊
兄弟元件(同一個父元件的子元件)之間需要通訊時,也是透過「狀態提升」:
function Parent() {
const [selectedId, setSelectedId] = useState(null)
return (
<div>
{/* 列表選擇 */}
<ItemList selectedId={selectedId} onSelect={setSelectedId} />
{/* 顯示詳情 */}
<ItemDetail itemId={selectedId} />
</div>
)
}
function ItemList({ selectedId, onSelect }) {
const items = [
{ id: 1, name: '項目 1' },
{ id: 2, name: '項目 2' },
{ id: 3, name: '項目 3' },
]
return (
<ul>
{items.map((item) => (
<li
key={item.id}
onClick={() => onSelect(item.id)}
style={{
fontWeight: selectedId === item.id ? 'bold' : 'normal',
cursor: 'pointer',
}}
>
{item.name}
</li>
))}
</ul>
)
}
function ItemDetail({ itemId }) {
if (!itemId) return <p>請選擇一個項目</p>
return <p>已選擇項目 ID:{itemId}</p>
}
跨多層元件傳遞:Context
當需要將資料傳遞給很深層的子元件時,一層一層透過 props 傳遞會很麻煩(prop drilling)。這時可以使用 Context:
import { createContext, useContext, useState } from 'react'
// 建立 Context
const UserContext = createContext(null)
// Provider 元件
function UserProvider({ children }) {
const [user, setUser] = useState({ name: 'Mike' })
return <UserContext value={{ user, setUser }}>{children}</UserContext>
}
// 任何深度的子元件都可以使用
function DeepChild() {
const { user } = useContext(UserContext)
return <p>歡迎,{user.name}!</p>
}
// 使用
function App() {
return (
<UserProvider>
<div>
<Header />
<MainContent>
<Sidebar>
<DeepChild /> {/* 不需要一層一層傳 props */}
</Sidebar>
</MainContent>
</div>
</UserProvider>
)
}
通訊模式比較
| 模式 | 適用情境 |
|---|---|
| Props 向下 | 父元件 → 子元件 |
| 回調函式 | 子元件 → 父元件 |
| 狀態提升 | 兄弟元件共享狀態 |
| Context | 跨多層傳遞、全域狀態 |
| useReducer + Context | 複雜的全域狀態管理 |
實際範例:表單與預覽
import { useState } from 'react'
function ArticleEditor() {
// 狀態在父元件
const [article, setArticle] = useState({
title: '',
content: '',
})
function handleChange(field, value) {
setArticle((prev) => ({
...prev,
[field]: value,
}))
}
return (
<div className="editor-container">
{/* 編輯區 */}
<EditorForm article={article} onChange={handleChange} />
{/* 預覽區 */}
<PreviewPane article={article} />
</div>
)
}
function EditorForm({ article, onChange }) {
return (
<div className="editor-form">
<h2>編輯</h2>
<input
type="text"
placeholder="標題"
value={article.title}
onChange={(e) => onChange('title', e.target.value)}
/>
<textarea
placeholder="內容"
value={article.content}
onChange={(e) => onChange('content', e.target.value)}
/>
</div>
)
}
function PreviewPane({ article }) {
return (
<div className="preview-pane">
<h2>預覽</h2>
<article>
<h1>{article.title || '無標題'}</h1>
<p>{article.content || '無內容'}</p>
</article>
</div>
)
}
何時使用哪種模式?
- 簡單的父子關係:Props + 回調函式
- 兄弟元件共享:狀態提升
- 跨 2-3 層:考慮重構元件結構,或使用 Context
- 跨多層且經常使用:Context
- 複雜的全域狀態:useReducer + Context