《React 元件基礎介紹》:
- React 的開發邏輯:為什麼現代前端要用元件?
- React 元件是什麼?畫面積木的基本單位
- 什麼是元件樹?理解元件的父子與巢狀結構
- 撰寫第一個 React 元件:函式型元件入門
- 如何規劃與拆分元件?從 UI 切割建立模組思維
- 元件之間如何傳資料?Props 教學入門
- 初學者也能懂!React children 全解析與實戰教學
- React 元件可以有自己的資料?state 基礎觀念與使用
- 什麼是 prop drilling?當資料傳遞變成麻煩
- Context API 是什麼?解決 prop drilling 的痛點
- 比 Context 更好用的選擇?認識第三方狀態管理工具
在閱讀本文前,建議先閱讀《JSX 基礎介紹》
在前幾篇文章中,我們學會了使用 props 把資料從父元件傳到子元件。
但你有沒有發現,如果資料需要從最上層一路傳到底層,每個中間層都得「中轉」一次,即使它們根本不需要這些資料?
這種現象就叫做 prop drilling(層層傳遞的困擾)。
這篇文章就要來介紹一個專門解決這種情況的 React 工具:Context API。
什麼是 Context API?
在 React 中,資料的傳遞通常是透過 props 一層一層往下傳的。
雖然這是 React 推崇的單向資料流設計,但當你的資料需要從最上層元件一路傳到很深的子元件時,整個資料流就會變得冗長又難維護。
這種情況下,React 提供了一套官方內建的解法:Context API。
Context API 是什麼?
Context API 是一套讓資料能夠在元件樹中全域共享的機制。
簡單來說,它有兩個功能:
- 定義一個資料來源:你可以創造一個「資料的倉庫」(Context)。
- 讓所有子元件都能存取這筆資料:無論它有多深,只要你有使用這個 Context,就能直接「拿到資料」,不用 props 一層一層傳。
🧠 用生活比喻幫助理解
想像你在公司開會,要傳一份資料給坐在你旁邊的同事,只需要輕鬆遞給他(就像 props 傳資料)。
但如果你要傳給坐在辦公室最角落的主管,就得一個一個人傳過去,非常麻煩(這就是 prop drilling 的困擾)。
Context API 就像是一個「共用雲端資料夾」,只要你把資料放進去,辦公室任何人只要有權限(用對方法),都能即時看到裡面的東西,不用再層層轉交。
Context 的核心結構
使用 Context API 主要分三個步驟:
| 步驟 | 說明 | 關鍵詞 |
|---|---|---|
| 建立 Context | 創造一個共享資料的空間 | createContext() |
| 提供資料 | 把資料放進去,讓後代元件能用 | <Provider value={...}> |
| 使用資料 | 在需要的元件中讀取資料 | useContext() |
這樣一來,你的資料就不再需要透過父層→子層→孫層一路傳,而是能直接在子層使用,大幅簡化資料流的設計與程式碼的可讀性。
Context API 的誕生目的
Context 的原始設計目的,就是為了解決這兩個問題:
- 當資料需要在多個元件之間共享
- 當資料被很多「中間元件」傳來傳去但根本沒使用它時
例如使用者登入狀態(user)、語系設定(locale)、主題模式(dark/light theme)這類需要「多處使用」又「希望統一管理」的資訊,就非常適合用 Context 來處理。
用 prop drilling 寫法會有多麻煩?
在 React 裡,資料傳遞的預設方式是「由父傳子」,也就是透過 props 把資料一層一層往下傳。
這樣的做法在元件層級不深時很直覺,但當元件架構變複雜,就會產生明顯的痛點。
❌ 例子:層層傳遞 props 的寫法
來看一個簡單的例子:我們希望把使用者名稱 user 傳到最底層的元件 GrandChild 顯示出來:
function App() {
return <Parent user="小明" />;
}
function Parent({ user }) {
return <Child user={user} />;
}
function Child({ user }) {
return <GrandChild user={user} />;
}
function GrandChild({ user }) {
return <h1>你好,{user}!</h1>;
}
雖然只有三層元件,但我們卻要在每一層都重複加上
user的props,哪怕Parent和Child根本完全不需要用到這個資料。
這就是所謂的 prop drilling:資料被「強迫經過」不需要它的元件。
為什麼這樣寫會變麻煩?
| 問題 | 說明 |
|---|---|
| 程式碼冗長 | 每一層都要接 props → 傳給下一層 |
| 難以維護 | 要改變資料名稱或型態,所有中繼元件都得跟著改 |
| 可讀性差 | 初學者會一直追「資料是哪來的」才能理解整體邏輯 |
| 重構成本高 | 萬一要調整元件結構,資料流也得重改一輪 |
在實際開發中,畫面組成通常是由好幾十個元件構成的樹狀結構,如果每次都得這樣傳資料,會讓維護與擴充變得非常痛苦。
使用 Context 解決資料層層傳遞的問題
在上一段,我們提到使用 props 會造成「層層傳遞」的問題(prop drilling)。這不僅讓程式碼變得冗長、難維護,還容易出錯。
為了解決這個問題,React 提供了 Context API,它能讓資料像「廣播一樣」傳遞給需要它的元件,而不需要經過中間所有的元件。
你只要三個步驟,就能輕鬆建立這種「跨層資料共享」的架構。
第一步:建立 Context(創建資料倉庫)
import { createContext } from 'react';
const UserContext = createContext();這行程式碼會建立一個 Context 物件,我們取名叫 UserContext。
它可以想像成是一個「共用資料的倉庫」,我們會把想要分享的資料(例如使用者名稱)放進這個倉庫,之後其他元件就能從這裡直接取用。
✅ 小提醒:一個專案裡可以建立多個 Context,
例如:
ThemeContext、AuthContext、LanguageContext等,依照資料類型分開管理比較好。
第二步:使用 Provider 提供資料(設立廣播站)
function App() {
return (
<UserContext.Provider value="小明">
<Parent />
</UserContext.Provider>
);
}
Context 物件有一個內建的元件:<Provider>,這個元件就像是廣播資料的站台,只要你把它包在元件外層,裡面的所有後代元件都可以接收到它提供的資料。
這裡我們設定 value="小明",表示要分享的資料就是字串 "小明",這會變成 UserContext 的共享內容。
✅ 不論
Parent有幾層、GrandChild多深,只要它們有包在Provider裡,就可以透過UserContext取得"小明"。
第三步:在任意元件中取得資料(用 useContext)
import { useContext } from 'react';
function GrandChild() {
const user = useContext(UserContext);
return <h1>你好,{user}!</h1>;
}
在你想要使用資料的元件中,只需要這兩個步驟:
- 匯入
useContext(React 提供的 Hook) - 呼叫
useContext(UserContext)取得資料
這樣 GrandChild 就可以直接取得 "小明",完全不用再從 props 傳進來了!
三個角色的比喻整理
| 名稱 | 比喻 | 功能 |
|---|---|---|
createContext() | 建立一個雲端資料夾 | 準備好放資料的容器 |
<Provider value={資料}> | 管理者上傳資料到雲端 | 設定資料內容與共享範圍 |
useContext(Context) | 使用者登入雲端帳號存取資料 | 讀取資料的方式,不需要中繼人員 |
額外補充:Context 的 value 可以不只是字串!
Context 的 value 其實可以是 任何 JavaScript 值,不只字串,還可以是:
- 整個物件
- 函式(例如登出、切換語系的邏輯)
- 布林值(例如登入狀態)
- 陣列、數字等等
例如:
<UserContext.Provider value={{ name: '小明', logout: () => setUser(null) }} />然後在元件中使用:
const { name, logout } = useContext(UserContext);這種方式更靈活,也更接近真實專案的使用方式。
比較一下兩種寫法的差異:
| 項目 | 傳統 props 寫法 | 使用 Context 寫法 |
|---|---|---|
| 資料傳遞方式 | 一層一層傳遞 props | 任意子元件直接取得資料 |
| 中間元件負責傳遞 | ✅ 必須接收與傳遞 | ❌ 完全不需要參與 |
| 擴充與重構彈性 | 低,改結構會牽連到 props | 高,資料與結構分離 |
| 可讀性與維護性 | 差,越寫越亂 | 好,邏輯清晰簡單 |
Context 不會取代 props,而是補充它的不足:
props適合一對一傳遞、簡單資料傳遞Context適合處理「一對多」的共享資料,例如使用者狀態、主題設定、語系等
只要場景對,用 Context 可以讓你的程式碼更乾淨、元件更獨立、開發更順手!
Context 的常見應用場景
Context 是處理「跨層元件共用資料」的利器,尤其在一些全站或多元件都會使用的資訊上特別實用。
以下是幾個實務上常見的使用場景:
| 應用情境 | 說明 | 常見變數名稱 |
|---|---|---|
| 登入資訊 | 儲存目前登入的使用者資訊,例如姓名、Email、權限、是否已登入等。 通常整個網站都會依據這個資訊做不同顯示(例如顯示「登出」按鈕、或跳轉到會員頁)。 | user、auth |
| 主題切換 | 在暗色(Dark Mode)與亮色(Light Mode)之間切換,這類資料會影響全站的樣式,適合放在 Context 裡供所有元件引用。 | theme |
| 語系設定(i18n) | 當你的網站支援多語系(繁中、英文、日文等),Context 可以幫你管理目前使用者選擇的語言設定。 | locale、language |
| 購物車狀態 | 在電商網站中,購物車會跨越多個頁面(首頁、商品頁、結帳頁…),需要統一維護 cart 的內容。Context 能幫你集中管理這些狀態。 | cart |
| 使用者偏好 | 比如使用者選擇的顯示模式(列表 or 圖卡)、排序依據、顯示的資料筆數等,這些偏好會被多個元件共用。 | preferences、settings |
🧠 小提醒:只要是「不想一直傳 props」,又「會被多個元件使用」的資料,都有可能適合放進 Context。
使用 Context 的注意事項
Context 雖然好用,但不是萬靈丹。使用上也要特別注意一些陷阱,否則反而會讓程式變得更複雜或效能下降。
不要濫用 Context:不是所有資料都該放進 Context
Context 最適合的場景是「少量、穩定、需要跨層共享」的資料。
如果你把所有東西都放進去(例如每個按鈕的 isOpen、每頁的頁碼、表單內容),會導致:
- 效能下降:只要 Context 的 value 有變動,所有使用它的元件都會重新渲染
- 邏輯混亂:資料集中但元件職責不清,每個元件都可以改資料,可能會互相干擾
✅ 建議原則:該局部的資料,就讓它局部;該全域的資料,再放進 Context。
記得使用 Provider 包住元件
Context 只在 Provider 的作用範圍內有效。如果你忘了包 <UserContext.Provider>,然後在子元件中使用:
const user = useContext(UserContext);你拿到的會是 undefined,程式可能會直接爆炸(因為你可能試圖取 user.name,但 user 是 undefined)。
✅ 解法:從最外層就包好 Provider,通常可以包在整個 App 的入口處,例如:
function App() {
return (
<UserContext.Provider value={{ name: '小明' }}>
<MainPage />
</UserContext.Provider>
);
}
Context 的 value 變動會觸發所有訂閱元件重新渲染
你可能會想:
「我每次都寫
value={{ name: '小明' }},資料都一樣,為什麼還會觸發更新?」
💡關鍵在於:React 不只看內容是不是一樣,而是看這是不是「同一個東西」
🧠 用一個「人名」的比喻
假設你跟 React 說:「這是 小明」,你給他一張紙,上面寫著 { name: '小明' }。
你下次又再說一次:「這還是 小明」,但這時候你是重新寫了一張一模一樣的紙。
React 會想:
「你又給我一張新紙,雖然看起來內容一樣,但不是同一張舊紙啊!應該是新的資料吧,那我就重新處理(re-render)!」
在 JavaScript 裡,這就是「物件參考不同」的意思:
{ name: '小明' } === { name: '小明' } // ❌ false雖然內容一樣,但因為是兩張不同的紙(不同的記憶體位置),JS 和 React 都會認為是「不同資料」。
🔄 再看看原本的程式碼
<UserContext.Provider value={{ name: '小明' }}>這行的物件 { name: '小明' } 是即時建立出來的新物件。
也就是說:
- 每次畫面更新(例如點擊按鈕或改變某個 state)
- App 這個元件會重新執行
- 然後這行
value={{ name: '小明' }}也會重新創造一個新的物件 - React 看到 value 是「新的東西」→ 會通知所有用
useContext的元件「資料變了喔!」→ 重新渲染全部子元件!
✅ 怎麼解決這件事?
如果你希望這張「小明的紙」是同一張紙(也就是記憶體中是同一個物件),你就要把它抽出去,不要每次都重新寫。
這時候就該用 useMemo():
const user = useMemo(() => ({ name: '小明' }), []);這樣做的意思是:
user這張紙只會被寫一次- 只要
[]沒變(表示永遠不重新寫),React 就會一直用這一張 value={user}→ React 發現是「同一個參考」→ 就不會誤以為資料有變動
總結:Context 是「共享資料」的橋樑
| 特性 | 說明 |
|---|---|
| 解決問題 | 避免 prop drilling 的混亂 |
| 資料流向 | Provider 提供 → Consumer (useContext) 取得 |
| 適用範圍 | 少量、全域性的資料,例如:登入狀態、主題、語系 |
| 風險提醒 | 不適合頻繁變動或大量狀態資料,否則會造成效能問題 |
雖然 Context 解決了 prop drilling,但在大型應用中還是不夠強大。
在下一篇文章,我們會來聊聊: 👉 「比 Context 更好用的選擇?認識第三方狀態管理工具」
介紹 Redux、Zustand、Jotai 等工具,幫助你在實務上選對解法!
