> ## Content Index
> Fetch the complete content index at: https://blog.anthea.day/llms.txt
> Use this file to discover other available public pages before exploring further.

# React 初心者大補帖（6）：跨元件共用的 Provider & Context
- URL: https://blog.anthea.day/series-react-beginner-6/
- Published: 2025-08-25T09:41:08.000Z
- Updated: 2026-11-07T19:59:23.000Z
- Author: Anthea / 芫荽
- Tags: #Migrated-1792241843742, #Import 2026-10-17 12:57, Coding, React 初心者大補帖

不管在使用 Vue 或是 React 開發時，常常遇到一個小困擾是：為了讓最底層的小組件拿到資料，需要一層一層傳遞 props，在 trace code 的時候就要一層一層爬回去，還有可能會在中間被篡改值，最後爬到迷路。

以購物網站為例，假設我們需要把使用者資訊、主題設定、購物車狀態傳到各種組件裡：

```javascript
function App() { 
  const [user, setUser] = useState(null); 
  const [theme, setTheme] = useState('light'); 
  const [cart, setCart] = useState([]); 
 
  return ( 
    <Layout user={user} theme={theme} cart={cart} setUser={setUser} /*... 更多 props*/ > 
      {/* 其他組件 */} 
    </Layout> 
  ); 
} 
 
function Layout({ user, theme, cart, setUser, ... }) { 
  return ( 
    <div> 
      <Header user={user} theme={theme} cart={cart} /*... 又是一堆 props*/ /> 
      <Main user={user} theme={theme} /*...*/ /> 
      <Footer theme={theme} /*...*/ /> 
    </div> 
  ); 
} 
 
// 然後 Header、Main、Footer 又要繼續往下傳 QQ
```

這樣可能會造成：

1. **程式碼超冗長** ：中間層組件變成「傳話筒」，明明不需要這些資料卻要幫忙傳遞
2. **改東西超麻煩**：要加一個新的全域狀態？準備改一堆檔案吧
3. **重構變地獄**：想調整組件結構？先把 props 傳遞鏈理清楚再說
4. **性能莫名其妙變差** ： 不相關的 props 變動也會讓組件重新渲染

---

### What is Provider Context

Context 是 React 提供的一種「跨組件通訊」機制。

Provider 就像廣播電台，負責發送訊號（提供資料）；Consumer（通常用 useContext）就像收音機，負責接收訊號（使用資料）。而 Context 本身就是那個頻道，確保大家收聽的是同一個電台。這種方式特別適合那些「很多組件都需要」的資料，像是使用者資訊、主題設定、語言偏好等等。

#### Context 的三劍客

1. **Context 本體**：用 `createContext()` 建立的神奇盒子
2. **Provider**：負責把資料塞進盒子裡
3. **Consumer** ： 從盒子裡拿資料的方式（通常用 `useContext`）

基本套路就是這樣：

```javascript
// 1. 先建立一個 Context 
const ThemeContext = createContext(); 
 
// 2. 用 Provider 包起來，餵資料進去 
function ThemeProvider({ children }) { 
  const [theme, setTheme] = useState('light'); 
  return ( 
    <ThemeContext.Provider value={{ theme, setTheme }}> 
      {children} 
    </ThemeContext.Provider> 
  ); 
} 
 
// 3. 需要用的時候就拿出來 
function SomeComponent() { 
  const { theme, setTheme } = useContext(ThemeContext); 
  return <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}> 
    切換主題 
  </button>; 
}
```

讓我們把前面那個災難用 Provider 重構一下。首先，我們要先建立好 Provider 和 Context 的內容：

```javascript
// UserContext.js 
const UserContext = createContext(); 
 
export const UserProvider = ({ children }) => { 
  const [user, setUser] = useState(null); 
   
  return ( 
    <UserContext.Provider value={{ user, setUser, isLoggedIn: !!user }}> 
      {children} 
    </UserContext.Provider> 
  ); 
}; 
 
export const useUser = () => useContext(UserContext); 
 
// ThemeContext.js   
const ThemeContext = createContext(); 
 
export const ThemeProvider = ({ children }) => { 
  const [theme, setTheme] = useState('light'); 
   
  return ( 
    <ThemeContext.Provider value={{ theme, setTheme }}> 
      {children} 
    </ThemeContext.Provider> 
  ); 
}; 
 
export const useTheme = () => useContext(ThemeContext);
```

接著組合一下：

```javascript
function AppProviders({ children }) { 
  return ( 
    <UserProvider> 
      <ThemeProvider> 
        <CartProvider> 
          {children} 
        </CartProvider> 
      </ThemeProvider> 
    </UserProvider> 
  ); 
} 
 
// 重構後的 App - 超清爽！ 
function App() { 
  return ( 
    <AppProviders> 
      <Layout /> 
    </AppProviders> 
  ); 
} 
 
// Layout 不用再當傳話筒了 
function Layout() { 
  return ( 
    <div> 
      <Header /> 
      <Main /> 
      <Footer /> 
    </div> 
  ); 
} 
 
// Header 想要什麼資料就自己拿 
function Header() { 
  const { user } = useUser(); 
  const { theme } = useTheme(); 
   
  return <header className={theme}>歡迎 {user?.name}！</header>; 
}
```

是不是瞬間清爽很多？

---

### 小心！別讓 Provider 打架 ⚠️

有時候在複雜的應用裡，你可能會意外地建立巢狀的 Provider，就像這樣：

```javascript
function AppProviders({ children }) { 
  return ( 
    <UserProvider> 
      <ThemeProvider> 
        <CartProvider> 
          {children} 
        </CartProvider> 
      </ThemeProvider> 
    </UserProvider> 
  ); 
} 
 
function App() { 
  return ( 
    <AppProviders> 
      <Header /> 
      <Content /> 
      <Footer /> 
    </AppProviders> 
  ); 
} 
 
function Layout() { 
  return ( 
    <UserProvider /* 糟糕！又有一個 UserProvider */> 
      <Main /> 
    </UserProvider> 
  ); 
}
```

這會造成內層的組件拿到內層 Provider 的資料，外層的組件拿到外層 Provider 的資料，就會造成不預期的結果。像是我在外層的 `UserProvider` 寫 user name 是 Anthea，內層的 `UserProvider` 是香菜，這時候我在 `Header` 拿到的 user name 和 `Main` 拿到的 user name 就會不一樣。

#### 要怎麼避免？

如果想避免這種情況，最簡單的就是加個檢查機制：

```typescript
const checkNestedProvider = <T extends any>(context: React.Context<T | null>) => { 
    const existingContext = useContext(context); 
    if (existingContext !== null) { 
        throw new Error("Provider cannot be nested. A provider is already present in the component tree."); 
    } 
}; 
 
// 在 Provider 裡面用 
export const UserProvider = ({ children }) => { 
  checkNestedProvider(UserContext); // 檢查有沒有重複 
  const [user, setUser] = useState(null); 
   
  return ( 
    <UserContext.Provider value={{ user, setUser }}> 
      {children} 
    </UserContext.Provider> 
  ); 
};
```

這樣如果有人不小心建立了巢狀 Provider，就會立刻收到錯誤訊息，避免奇怪的 bug！

---

用了 Provider Context 之後，你會發現：

- **再也不用玩傳話遊戲**：直接從 Context 拿資料，超方便
- **程式碼變得超乾淨**：中間層組件不用再當傳話筒
- **維護變簡單**：要改全域狀態只要改 Provider 就好
- **重構不再是惡夢** ： 組件之間的耦合度大幅降低

當然，Context 也不是萬能的。如果只是簡單的父子組件傳資料，直接用 props 可能更直觀；如果是超複雜的狀態管理，可能需要 Redux 或 Zustand 更容易處理。

但在大部分情況下，Context 都是個很棒的選擇。它是 React 內建的，學會了就能在各種專案裡使用，而且概念簡單、上手容易。

以上就是今天的分享，我們下次見 👋🏻