useContext は、遠い親が提供した値を props の中継なしで読む Hook です。テーマ、現在のユーザー、画面の表示密度のように、多くの子孫が同じ値を必要とするときに使います。
props の中継を減らす
値を使わない中間コンポーネントまで props を渡し続けると、構造の変更に弱くなります。Context は共有したい値の通り道を作ります。ただし、近い親子のデータ受け渡しまで Context にする必要はありません。
import { createContext, useContext } from 'react';
const ThemeContext = createContext<'light' | 'dark'>('light');
function SaveButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>保存</button>;
}
export function Settings() {
return (
<ThemeContext value="dark">
<SaveButton />
</ThemeContext>
);
}
useContext(ThemeContext) は最も近い Provider の value を読み、値が変わればその Context を読むコンポーネントを再レンダーします。React 19 より前の書き方では <ThemeContext.Provider value="dark"> を使います。
値は Provider から下へ流れます。途中のコンポーネントが props を受け取らなくても、子孫は最寄りの Provider を探せます。同じ Context の Provider を入れ子にした場合は、近い方の値が優先されます。親が自分の Provider を返していても、その親自身の useContext は外側の Provider を読みます。
state と組み合わせる
Context は値を保存する機能ではありません。共有する値が変化するなら、Provider 側で useState や useReducer を使います。読み取り用の値と更新関数を必要な範囲にだけ公開します。
const AuthContext = createContext<{ signedIn: boolean; signOut: () => void } | null>(null);
function Profile() {
const auth = useContext(AuthContext);
if (auth === null) throw new Error('AuthContext が必要です');
return <button onClick={auth.signOut}>{auth.signedIn ? 'ログアウト' : 'ログイン'}</button>;
}
Provider を読む側で毎回 null 判定を書く代わりに、専用 Hook に閉じ込める方法もあります。
function useAuth() {
const auth = useContext(AuthContext);
if (auth === null) throw new Error('AuthProvider の内側で使ってください');
return auth;
}
この形なら利用側は const { signedIn, signOut } = useAuth() と書けます。Provider を置き忘れたときも、デフォルト値で静かに動き続けるのではなく、開発中に原因を特定できます。
使う判断と落とし穴
アプリ全体の設定や、離れた複数箇所で読む安定した共有値には向いています。一方、頻繁に変わる大きなオブジェクトを1つの Context に入れると、値を読む広い範囲が更新されます。Context を目的別に分け、まずは props・コンポーネント合成で表現できないかを検討します。
Provider がない場合は createContext のデフォルト値が返ります。開発時の不備を検出したい必須 Context には null を初期値にして、専用 Hook でエラーにする設計が安全です。
Context の値にオブジェクトや関数を渡す場合は、その参照が変わるたびに購読者へ更新が届きます。問題が実測できたときだけ useMemo と useCallback で Provider の値を安定させます。更新頻度が異なる値を同じ Context に詰め込まないことが、まず効く最適化です。
Context は「グローバル変数」ではない
Context はコンポーネントツリーの特定の範囲に値を提供します。そのため、テストでは対象を Provider で包んで値を差し替えられます。また、同じ画面で別々のテーマや言語を扱うこともできます。モジュールのトップレベルに可変値を置くグローバル変数とは、更新通知とスコープの点で異なります。
一方で、何でも Context に入れると依存関係が JSX から見えなくなります。「このボタンは theme と auth に依存する」という事実を追いにくくなるため、親子数段の受け渡しなら props の方が明快です。データを渡す代わりに children を渡すコンポーネント合成も、有力な選択肢です。
まとめ
useContext は props のバケツリレーを避けるための共有経路です。状態管理そのものではないため、共有範囲と更新頻度を小さく保ちましょう。複雑な更新規則を共有するときは次に useReducer を検討します。