第77章:Provider を分ける
1️⃣ 今日のゴール 🎯
この章のゴールは、これです👇
「Context の
Providerを、App.tsxから外に出してスッキリさせる」
- Context 自体はもう知ってる想定だよね(
createContext/Provider/useContext/ カスタムフックなど)。(React) - でも、その
Providerをどこに置くか は、設計の話になるので、ちゃんとパターンとして覚えておくとめちゃ便利です ✨
2️⃣ よくある「App.tsx がギュウギュウ問題」😵💫
まずは「よくない例」から。
全部 App.tsx に書いちゃってるパターンをイメージしてみましょう。
-
App.tsxの中でThemeContextを作ってuseStateでテーマを管理して- そのまま
<ThemeContext.Provider>で全体を包む
みたいな状態です。
// src/App.tsx(イマイチな例)💦
import { createContext, useState } from "react";
type Theme = "light" | "dark";
type ThemeContextValue = {
theme: Theme;
toggleTheme: () => void;
};
export const ThemeContext = createContext<ThemeContextValue | undefined>(
undefined
);
export function App() {
const [theme, setTheme] = useState<Theme>("light");
const value: ThemeContextValue = {
theme,
toggleTheme: () =>
setTheme((prev) => (prev === "light" ? "dark" : "light")),
};
return (
<ThemeContext.Provider value={value}>
{/* 本当はここにページやルーティングがいろいろ並ぶ */}
<h1>こんにちは!</h1>
</ThemeContext.Provider>
);
}
これでも動くし、最初はこれで全然OKなんだけど…
-
Context が増えるたびに
UserContext.ProviderSettingsContext.ProviderThemeContext.Provider…みたいに、App.tsxがネストだらけになります 🌀 -
App.tsxは- ルーティング
- レイアウト
- ページの組み立て などの「画面構成」に集中させたいのに、状態管理ロジックまで入り込んでくるのがつらいポイントです。
3️⃣ 図でイメージしてみる 🧠✨
まずは「全部 App.tsx に書いている」パターンを図にします。
これでも悪くはないんだけど、Context が増えると…
App.tsxが「なんでも倉庫」になってしまう、というイメージです 🧳🧳🧳
4️⃣ 方針:Provider を「専用コンポーネント」にする ✂️
そこで第77章のアイデアはこれ👇
Context ごとに「Provider 専用コンポーネント」を作って、
App.tsxから追い出す
今回の例では ThemeContext を使って説明しますが、この考え方は
どんな Context にも共通で使えます ☺️
5️⃣ ステップ①:Context を専用ファイルにお引っ越し 📦
まずは、ThemeContext 用のファイルを作ります。
👉 例:src/contexts/ThemeContext.tsx
この中に
- 型定義(
ThemeとThemeContextValue) ThemeContext本体ThemeProviderコンポーネントuseThemeカスタムフック
までを全部まとめてしまいます。
// src/contexts/ThemeContext.tsx ✨
import {
createContext,
useContext,
useState,
type ReactNode,
} from "react";
type Theme = "light" | "dark";
type ThemeContextValue = {
theme: Theme;
toggleTheme: () => void;
};
// 76章でやった「undefined を許す」スタイルと同じノリ 🧠
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
type ThemeProviderProps = {
children: ReactNode;
};
export function ThemeProvider({ children }: ThemeProviderProps) {
const [theme, setTheme] = useState<Theme>("light");
const value: ThemeContextValue = {
theme,
toggleTheme: () =>
setTheme((prev) => (prev === "light" ? "dark" : "light")),
};
return (
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
);
}
// 76章の復習:安全なカスタムフック 🔒
export function useTheme() {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error("useTheme は ThemeProvider の中で使ってください 🌙");
}
return context;
}
これで、
- Context 関連のものは
ThemeContext.tsxに集結 - 他のファイルからは
ThemeProviderとuseThemeだけ知っていればOK
という、きれいな状態になります ✨
6️⃣ ステップ②:App.tsx からロジックを追い出す 🏃♀️💨
次に App.tsx を、画面構成だけ担当する子にしてあげます。
👉 例:src/App.tsx
// src/App.tsx 🎀
import { useTheme } from "./contexts/ThemeContext";
export function App() {
const { theme, toggleTheme } = useTheme();
return (
<div
style={{
padding: "16px",
backgroundColor: theme === "light" ? "#fff" : "#222",
color: theme === "light" ? "#222" : "#fff",
}}
>
<h1>第77章:Provider を分けてみたよ ✨</h1>
<p>いまのテーマ: {theme}</p>
<button onClick={toggleTheme}>テーマを切り替える🌗</button>
</div>
);
}
ここでポイントは 👇
Appは もうuseStateもしないしcreateContextもしない- ただ
useTheme()で「コンテキストの値」をもらうだけ - 「Theme をどう管理しているか」は
ThemeProviderの責任
→ App.tsx がかなりスッキリしてきましたね 🧼
7️⃣ ステップ③:どこで ThemeProvider で包むの?🤔
じゃあ、ThemeProvider はどこで使うの?という話です。
一番よくあるのは、main.tsx(Vite が作ってくれたエントリーポイント)でアプリ全体を包むパターンです。
👉 例:src/main.tsx
// src/main.tsx 🌸
import React from "react";
import ReactDOM from "react-dom/client";
import { App } from "./App";
import { ThemeProvider } from "./contexts/ThemeContext";
ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
).render(
<React.StrictMode>
<ThemeProvider>
<App />
</ThemeProvider>
</React.StrictMode>
);
こうすると、アプリ全体のどのコンポーネントからでも useTheme() が使えるようになります 💡
そして App.tsx 自体は、ただの「画面の組み立て係」に専念できます。
8️⃣ ステップ④:Provider が増えたら「AppProviders」で束ねよう 🎁
アプリが大きくなると、だいたいこんな感じになります👇
AuthProvider(ログイン状態)ThemeProvider(テーマ)UserSettingsProvider(ユーザー設定)CartProvider(カート情報)
そのたびに main.tsx でこんなふうにネストしていくと…🌀
// よくある「ネスト地獄」例 😇
ReactDOM.createRoot(...).render(
<React.StrictMode>
<AuthProvider>
<ThemeProvider>
<UserSettingsProvider>
<CartProvider>
<App />
</CartProvider>
</UserSettingsProvider>
</ThemeProvider>
</AuthProvider>
</React.StrictMode>
);
これでも動くんだけど、見た目がちょっとツラい…。
そこで登場するのが、「まとめ役 Provider」 です ✨
🧺 AppProviders という「まとめコンポーネント」を作る
👉 例:src/providers/AppProviders.tsx
// src/providers/AppProviders.tsx 💐
import type { ReactNode } from "react";
import { ThemeProvider } from "../contexts/ThemeContext";
// 将来的にここに追加していくイメージ
// import { AuthProvider } from "../contexts/AuthContext";
// import { UserSettingsProvider } from "../contexts/UserSettingsContext";
type AppProvidersProps = {
children: ReactNode;
};
export function AppProviders({ children }: AppProvidersProps) {
return (
// ここに「アプリ共通の Provider」を全部まとめる
// 順番もここで管理できるのがポイント 💡
// <AuthProvider>
// <UserSettingsProvider>
// <ThemeProvider>
// {children}
// </ThemeProvider>
// </UserSettingsProvider>
// </AuthProvider>
// 今回は Theme だけにしてシンプルに ✨
<ThemeProvider>{children}</ThemeProvider>
);
}
そして main.tsx はこうなります 👇
// src/main.tsx(スッキリ版)✨
import React from "react";
import ReactDOM from "react-dom/client";
import { App } from "./App";
import { AppProviders } from "./providers/AppProviders";
ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
).render(
<React.StrictMode>
<AppProviders>
<App />
</AppProviders>
</React.StrictMode>
);
これで
main.tsx→ 「アプリの一番外側」を担当AppProviders.tsx→ 「共通 Provider の並べ方」を担当App.tsx→ 「画面(ルーティング・レイアウト)」を担当
と、いい感じに役割分担ができました👏
9️⃣ 図で「まとめ Providers パターン」を確認 🧩
さっきの構成を Mermaid で図にしてみます。
ProviderのネストはAppProvidersの中だけ に閉じ込めるApp.tsxからは Context の管理ロジックが消えて、 かなり読みやすくなります ✨
🔟 どこまで分ければいいの?という感覚の話 🎈
「なんでもかんでも分ければいい」というわけではなくて、ざっくり次の感覚でOKです👇
-
Context の数が 1〜2 個くらい
- 最初は
App.tsxにあってもそこまで困らない
- 最初は
-
3 個以上になってきたら
ThemeProviderなどを専用ファイルに出す
-
「アプリ全体で使う Provider」が増えてきたら
AppProvidersでまとめる
-
あるページだけで使う Context
- そのページのフォルダの中で
Providerを作って、 - そのページのルートコンポーネントだけ包む → 「必要なところにだけかける」のがポイント 🎯
- そのページのフォルダの中で
1️⃣1️⃣ ミニ練習問題 ✏️✨
時間があれば、こんな練習をしてみてね👇
-
UserContextを作ってみるtype User = { id: number; name: string };UserContextValue = { user: User | null; setUser: (...) => void }UserProviderとuseUserをsrc/contexts/UserContext.tsxに作る
-
AppProvidersに追加するAppProvidersの中で<UserProvider>→<ThemeProvider>→{children}の順にネストしてみる
-
App.tsxからuseUser()を呼んでみるuserがnullなら「未ログイン」表示- テスト用にボタンでダミーのユーザーをセットしてみる
ここまでできたら、
「Provider を専用の場所に分ける」+「まとめて管理する」
という、第77章のテーマはバッチリです 💯🎉
1️⃣2️⃣ まとめ 🌸
-
App.tsxに Context ロジックを全部詰め込むと、あとで読みにくくなる😵💫 -
ThemeContext.tsxのように、Context ごとに専用ファイル+Provider コンポーネントを作るとスッキリ ✨ -
AppProvidersで 共通 Provider をひとまとめにすると、main.tsxもApp.tsxも 読みやすくてメンテしやすい構成になる 🧹
-
このパターンは、このあと出てくる
- ユーザー名 Context(第78章)
- ダークモード Context(第80章) でもそのまま使える「守備範囲広めのテクニック」です 💪
次の章からは、この分けた Provider を使って、実際にいろいろな場所にデータを配っていきますよ〜🥰