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)切換
- 使用者登入狀態控管
- 網站設定與權限管理