状態管理 レッスン2
useContext
React組み込みのグローバル状態管理を使いこなそう
createContextでコンテキストを作る
Contextは、Props Drillingを解消するReact組み込みの仕組みです。 まずcreateContextでコンテキストを作成します。
import { createContext } from "react";
// 型定義(TypeScript)
interface User {
name: string;
email: string;
avatar: string;
}
interface UserContextType {
user: User | null;
login: (user: User) => void;
logout: () => void;
}
// コンテキストを作成(デフォルト値を指定)
const UserContext = createContext<UserContextType>({
user: null,
login: () => {},
logout: () => {},
});
export default UserContext;createContextにはデフォルト値を渡します。 TypeScriptではジェネリクスで型を指定すると安全です。
Providerパターン
Providerはコンテキストの値を子コンポーネントに提供するラッパーです。 Providerの中にあるすべてのコンポーネントがコンテキストの値にアクセスできます。
import { useState, ReactNode } from "react";
import UserContext from "./UserContext";
// Providerコンポーネント
function UserProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const login = (userData: User) => {
setUser(userData);
};
const logout = () => {
setUser(null);
};
return (
<UserContext.Provider value={{ user, login, logout }}>
{children}
</UserContext.Provider>
);
}
// アプリのルートでProviderを配置
function App() {
return (
<UserProvider>
<Header />
<MainContent />
<Footer />
</UserProvider>
);
}
// UserProviderの中にある全コンポーネントが
// user, login, logout にアクセスできるProviderのvalueに渡したオブジェクトが、 子孫コンポーネントから参照できるようになります。
useContextフックで値を取得
子コンポーネントではuseContextフックを使って コンテキストの値を取得します。Propsを経由せず、どの階層からでも直接アクセスできます。
import { useContext } from "react";
import UserContext from "./UserContext";
// カスタムフックにするとさらに便利
function useUser() {
const context = useContext(UserContext);
if (context === undefined) {
throw new Error("useUser must be used within a UserProvider");
}
return context;
}
// 使用例:ヘッダーコンポーネント
function Header() {
const { user, logout } = useUser();
return (
<header>
{user ? (
<div>
<span>こんにちは、{user.name}さん</span>
<button onClick={logout}>ログアウト</button>
</div>
) : (
<button>ログイン</button>
)}
</header>
);
}
// 使用例:プロフィールページ(深い階層でも直接アクセス)
function ProfilePage() {
const { user } = useUser();
if (!user) return <p>ログインしてください</p>;
return (
<div>
<img src={user.avatar} alt={user.name} />
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}カスタムフック(useUser)にまとめると、 エラーチェックの重複を避け、使い勝手が良くなります。
複数のContextを組み合わせる
関心事ごとにContextを分離すると、コードが整理されます。 複数のProviderをネストして使います。
// テーマ用のContext
const ThemeContext = createContext<{
theme: "light" | "dark";
toggleTheme: () => void;
}>({ theme: "light", toggleTheme: () => {} });
// 言語用のContext
const LanguageContext = createContext<{
lang: "ja" | "en";
setLang: (lang: "ja" | "en") => void;
}>({ lang: "ja", setLang: () => {} });
// 複数のProviderをネスト
function App() {
return (
<ThemeProvider>
<LanguageProvider>
<UserProvider>
<MainApp />
</UserProvider>
</LanguageProvider>
</ThemeProvider>
);
}
// Providerが増えたら統合するヘルパーも作れる
function AppProviders({ children }: { children: ReactNode }) {
return (
<ThemeProvider>
<LanguageProvider>
<UserProvider>
{children}
</UserProvider>
</LanguageProvider>
</ThemeProvider>
);
}Contextの限界と注意点
Contextは強力ですが、万能ではありません。以下の点に注意しましょう。
// 問題1: 不要な再レンダリング
// Contextの値が変わると、useContextを使っている
// 全コンポーネントが再レンダリングされる
const AppContext = createContext({
user: null,
theme: "light",
notifications: [],
});
// user だけ変わっても、theme や notifications を
// 使っているコンポーネントも再レンダリングされる!
// 対策: Contextを分割する
const UserContext = createContext(null); // ユーザー用
const ThemeContext = createContext("light"); // テーマ用
const NotifContext = createContext([]); // 通知用
// 問題2: 頻繁な更新には不向き
// アニメーション、マウス位置、テキスト入力のリアルタイム追跡など
// 高頻度の更新にはContextは向かない → Zustandなどを検討
// Contextが適切なケース:
// ✅ テーマ(ダーク/ライト切り替え)
// ✅ ログインユーザー情報
// ✅ 言語設定
// ✅ ルーティング情報
// 外部ライブラリが適切なケース:
// ✅ 頻繁に更新されるデータ
// ✅ 複雑な状態遷移ロジック
// ✅ ミドルウェア(ログ、永続化)が必要
// ✅ 大規模なチーム開発まとめ
createContextでコンテキストを作成し、Providerで値を提供するuseContextフックで、どの階層からでもコンテキストの値にアクセスできる- カスタムフックにまとめるとエラーチェックが統一でき、使い勝手が向上する
- 関心事ごとにContextを分割すると不要な再レンダリングを防げる
- 頻繁な更新や複雑な状態ロジックには外部ライブラリの方が適している