Context API 是什麼?解決 prop drilling 的痛點

Published April 13, 2025 by 徐培鈞
JavaScript
Context API 是什麼?解決 prop drilling 的痛點

在前幾篇文章中,我們學會了使用 props 把資料從父元件傳到子元件。

但你有沒有發現,如果資料需要從最上層一路傳到底層,每個中間層都得「中轉」一次,即使它們根本不需要這些資料?

這種現象就叫做 prop drilling(層層傳遞的困擾)。

這篇文章就要來介紹一個專門解決這種情況的 React 工具:Context API


什麼是 Context API?

在 React 中,資料的傳遞通常是透過 props 一層一層往下傳的。

雖然這是 React 推崇的單向資料流設計,但當你的資料需要從最上層元件一路傳到很深的子元件時,整個資料流就會變得冗長又難維護

這種情況下,React 提供了一套官方內建的解法:Context API

Context API 是什麼?

Context API 是一套讓資料能夠在元件樹中全域共享的機制。

簡單來說,它有兩個功能:

  1. 定義一個資料來源:你可以創造一個「資料的倉庫」(Context)。
  2. 讓所有子元件都能存取這筆資料:無論它有多深,只要你有使用這個 Context,就能直接「拿到資料」,不用 props 一層一層傳

🧠 用生活比喻幫助理解

想像你在公司開會,要傳一份資料給坐在你旁邊的同事,只需要輕鬆遞給他(就像 props 傳資料)。

但如果你要傳給坐在辦公室最角落的主管,就得一個一個人傳過去,非常麻煩(這就是 prop drilling 的困擾)。

Context API 就像是一個「共用雲端資料夾」,只要你把資料放進去,辦公室任何人只要有權限(用對方法),都能即時看到裡面的東西,不用再層層轉交。

Context 的核心結構

使用 Context API 主要分三個步驟:

說明創造一個共享資料的空間
關鍵詞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>;
}

雖然只有三層元件,但我們卻要在每一層都重複加上 userprops,哪怕 ParentChild 根本完全不需要用到這個資料

這就是所謂的 prop drilling:資料被「強迫經過」不需要它的元件。

為什麼這樣寫會變麻煩?

說明每一層都要接 props → 傳給下一層
說明要改變資料名稱或型態,所有中繼元件都得跟著改
說明初學者會一直追「資料是哪來的」才能理解整體邏輯
說明萬一要調整元件結構,資料流也得重改一輪

在實際開發中,畫面組成通常是由好幾十個元件構成的樹狀結構,如果每次都得這樣傳資料,會讓維護與擴充變得非常痛苦。


使用 Context 解決資料層層傳遞的問題

在上一段,我們提到使用 props 會造成「層層傳遞」的問題(prop drilling)。這不僅讓程式碼變得冗長、難維護,還容易出錯。

為了解決這個問題,React 提供了 Context API,它能讓資料像「廣播一樣」傳遞給需要它的元件,而不需要經過中間所有的元件。

你只要三個步驟,就能輕鬆建立這種「跨層資料共享」的架構。

第一步:建立 Context(創建資料倉庫)

import { createContext } from 'react';

const UserContext = createContext();

這行程式碼會建立一個 Context 物件,我們取名叫 UserContext

它可以想像成是一個「共用資料的倉庫」,我們會把想要分享的資料(例如使用者名稱)放進這個倉庫,之後其他元件就能從這裡直接取用。

✅ 小提醒:一個專案裡可以建立多個 Context,

例如:ThemeContextAuthContextLanguageContext 等,依照資料類型分開管理比較好。

第二步:使用 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>;
}

在你想要使用資料的元件中,只需要這兩個步驟:

  1. 匯入 useContext(React 提供的 Hook)
  2. 呼叫 useContext(UserContext) 取得資料

這樣 GrandChild 就可以直接取得 "小明"完全不用再從 props 傳進來了!

三個角色的比喻整理

比喻建立一個雲端資料夾
功能準備好放資料的容器
比喻管理者上傳資料到雲端
功能設定資料內容與共享範圍
比喻使用者登入雲端帳號存取資料
功能讀取資料的方式,不需要中繼人員

額外補充:Context 的 value 可以不只是字串!

Context 的 value 其實可以是 任何 JavaScript 值,不只字串,還可以是:

  • 整個物件
  • 函式(例如登出、切換語系的邏輯)
  • 布林值(例如登入狀態)
  • 陣列、數字等等

例如:

<UserContext.Provider value={{ name: '小明', logout: () => setUser(null) }} />

然後在元件中使用:

const { name, logout } = useContext(UserContext);

這種方式更靈活,也更接近真實專案的使用方式。

比較一下兩種寫法的差異:

傳統 props 寫法一層一層傳遞 props
使用 Context 寫法任意子元件直接取得資料
傳統 props 寫法✅ 必須接收與傳遞
使用 Context 寫法❌ 完全不需要參與
傳統 props 寫法低,改結構會牽連到 props
使用 Context 寫法高,資料與結構分離
傳統 props 寫法差,越寫越亂
使用 Context 寫法好,邏輯清晰簡單

Context 不會取代 props,而是補充它的不足:

  • props 適合一對一傳遞、簡單資料傳遞
  • Context 適合處理「一對多」的共享資料,例如使用者狀態、主題設定、語系等

只要場景對,用 Context 可以讓你的程式碼更乾淨、元件更獨立、開發更順手!


Context 的常見應用場景

Context 是處理「跨層元件共用資料」的利器,尤其在一些全站或多元件都會使用的資訊上特別實用。

以下是幾個實務上常見的使用場景

說明儲存目前登入的使用者資訊,例如姓名、Email、權限、是否已登入等。通常整個網站都會依據這個資訊做不同顯示(例如顯示「登出」按鈕、或跳轉到會員頁)。
常見變數名稱user、auth
說明在暗色(Dark Mode)與亮色(Light Mode)之間切換,這類資料會影響全站的樣式,適合放在 Context 裡供所有元件引用。
常見變數名稱theme
說明當你的網站支援多語系(繁中、英文、日文等),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 等工具,幫助你在實務上選對解法!