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>
  )
}

重點:

  • 狀態(temperaturescale)存在父元件
  • 子元件透過 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>
  )
}

何時使用哪種模式?

  1. 簡單的父子關係:Props + 回調函式
  2. 兄弟元件共享:狀態提升
  3. 跨 2-3 層:考慮重構元件結構,或使用 Context
  4. 跨多層且經常使用:Context
  5. 複雜的全域狀態:useReducer + Context