useContext 入門:跨層資料傳遞不再痛苦

更新日期: 2025 年 4 月 19 日

你是否曾經遇過這樣的情境:一個全域資料(像是使用者登入狀態、主題模式、語言設定),必須從最上層的元件層層傳遞到子孫元件?

這種層層傳遞 props 的痛苦,我們稱為 prop drilling

當你的元件結構越來越深,維護成本也隨之升高。

為了解決這個問題,React 提供了一個內建機制 —— Context API,讓你能夠輕鬆地跨層傳遞資料,不用再一層一層傳 props。


為什麼需要 useContext?

prop drilling 的問題

在 React 中,元件之間的資料傳遞通常是透過 props(屬性),由父元件往子元件一層一層地傳下去。

然而,當你想要將資料從最上層的元件傳到最底層時,如果中間的元件根本不需要這些資料,卻仍然被迫參與傳遞,這就會造成我們所說的 prop drilling(層層鑽孔)問題

讓我們看一個例子👇

假設你的元件結構如下:

<App>
  └─ <Page>
       └─ <Toolbar>
            └─ <Button>

你的需求是:讓最底層的 <Button /> 元件知道目前的「主題」是 light 還是 dark,來決定按鈕的樣式。

如果你沒有使用 Context,只能靠 props 傳遞來做到這件事:

function App() {
  return <Page theme="light" />;
}

function Page({ theme }) {
  return <Toolbar theme={theme} />;
}

function Toolbar({ theme }) {
  return <Button theme={theme} />;
}

function Button({ theme }) {
  return <button className={theme}>按鈕</button>;
}

你會發現:

  • <Button /> 才是 唯一需要用到 theme 的元件
  • <Page /><Toolbar /> 這兩個中間層也 不得不接收 theme 並往下傳
  • 如果未來結構更複雜,資料要傳到第 5 層、第 10 層……每一層都要寫 props,維護起來就會越來越痛苦。

這種狀況不只會讓程式碼變得 冗長、難懂、難維護,還容易造成 bug,尤其當你需要修改或新增某個參數時,得沿路一路更新所有中間層。

用 Context 優雅解決跨層資料

為了解決這個問題,React 提供了 Context API,搭配 useContext hook,就可以輕鬆做到「跨層資料共享」,不再需要中間層做轉運站!

Context 的基本概念分成三個步驟:

1️⃣ 定義一份「共享資料」的空間(Context)

你可以把它想成是一個「資料的倉庫」,裡面可以放任何你想要跨層傳遞的資料。

2️⃣ 在最上層的元件使用 Provider 提供資料

就像你把資料放進倉庫一樣,Provider 負責「灌資料進去」,讓整個子元件樹都可以取用這些資料。

3️⃣ 在任意子層使用 useContext 拿資料

不管資料倉庫(Provider)在第幾層,任何子元件只要用 useContext 就能直接取得這份資料,不需要透過 props 一層層傳遞


基本使用方式:三步驟學會 useContext

建立 Context —— 全域共享資料的「倉庫」

在 React 中,我們可以透過 createContext() 這個函式,來建立一個「共享資料的容器」,這個容器可以讓整個元件樹中的子元件共用一份資料,而不必層層透過 props 傳遞。

// ThemeContext.js
import { createContext } from 'react';

// 建立 ThemeContext,預設值為 'light'
export const ThemeContext = createContext('light');

這個 ThemeContext 是一個「Context 物件」,裡面包含:

  • ThemeContext.Provider:一個特殊的元件,用來提供資料給整棵元件樹。
  • ThemeContext.Consumer:React Context 在早期版本(還沒有 hook)所使用的寫法,現在大多數情況下會改用 useContext() hook,讓語法更簡潔。

✅ 我們在這個系列中只會使用 useContext(),讓你寫出更現代、可讀性更高的程式碼。

💡 小提醒:

  • createContext('light') 裡的 'light'預設值,只有當你沒有用 Provider 包住元件時,React 才會回傳這個預設值。
  • 實務上我們會將 Context 獨立寫在一個檔案中(如 ThemeContext.js),這樣可以在多個元件之間重複使用,方便維護與管理。

用 Provider 提供資料 —— 把資料「廣播」出去

現在你已經有了一個 Context 倉庫,要開始讓元件可以讀取資料,就需要用 ThemeContext.Provider 把資料「送進去」。

你可以把它想像成一個「資料廣播站」,會把 value 的內容傳遞給裡面的所有元件。

// App.js
import { ThemeContext } from './ThemeContext';
import Page from './Page';

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Page />
    </ThemeContext.Provider>
  );
}

這段程式碼代表:

  • 整個 <Page /> 和它的所有子孫元件,都會「共享」一份主題資料:"dark"
  • Provider 就像是把「資料」注入到整棵元件樹中,所有想要取用這份資料的元件,只要在這個 Provider 裡面,就能輕鬆拿到。

💡 小提醒:

  • Provider 不一定要放在最頂層(像是 <App />),你可以根據需求,把它包在任何一層元件中。
  • value 可以是一個字串、數字、物件,甚至是函式,讓你可以自由傳遞任何你想共享的東西。

在子元件中使用 useContext() 拿資料 —— 不再需要 props!

現在你的資料已經透過 Provider 廣播出去了,子元件就可以用 useContext() 這個 Hook,直接從資料倉庫中取用資料,不需要任何中繼傳遞!

// Button.js
import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

function Button() {
  const theme = useContext(ThemeContext); // ← 取得 Provider 提供的資料

  return <button className={theme}>我是 {theme} 主題按鈕</button>;
}

在這段程式碼中:

  • useContext(ThemeContext) 就像是一把鑰匙,打開資料倉庫並直接拿到你要的東西。
  • theme 的值就是 Provider 提供的 "dark"
  • <Button /> 不需要任何 props,也不需要知道自己在第幾層,它只要知道 ThemeContext 是什麼,就能取得資料!

📌 圖像化理解

ThemeContext.Provider (value: "dark")

├── Page
│   └── Toolbar
│       └── Button ← 直接用 useContext 拿到 "dark"

你會發現,整棵元件樹中,只有 <App /> 提供資料,只有 <Button /> 使用資料,中間的 <Page><Toolbar> 完全不用知道資料的存在,程式碼也更簡潔乾淨。

小結

步驟做什麼實作要點
1️⃣建立 Context 倉庫createContext(預設值)
2️⃣使用 Provider 提供資料<Context.Provider value={...}>
3️⃣使用 useContext() 拿資料const data = useContext(Context)

結語

useContext 是讓你寫出 更乾淨、更易維護 React 程式碼的利器。

在專案越來越大的情況下,它能幫你有效減少不必要的 props 傳遞,並集中管理全域狀態。

掌握它後,你可以輕鬆打造像是:

  • 主題切換(light / dark mode)
  • 多語系(i18n)切換
  • 使用者登入狀態控管
  • 網站設定與權限管理

Similar Posts