useContextは、createContextを介して作成されたコンテキストからデータに直接アクセスできるようにするReactフックです。Reactのコンテキストはprops drillingの問題 — このデータを自分では使用しない多数の中間コンポーネントを通じてpropsを渡すこと — を解決します。React Documentation (2025)によると、useContextはコンテキストオブジェクトを受け取り、コンポーネントツリーの上位にある最も近いProviderによって設定された現在の値を返します。Providerの値が変更されると、useContextを使用するすべてのコンポーネントが自動的に再レンダリングされます。
重要なポイント
useContextは、React 16.8で他のフックとともに追加された、Reactコンテキストから値を読み取るためのフックです。コンテキストはReactに組み込まれたメカニズムであり、各レベルで手動でpropsを渡す必要なくコンポーネントツリーを通じてデータを渡すように設計されています。useContextは古いContext APIのConsumerコンポーネントを置き換え、コードをより簡潔で読みやすくします。
コンテキストの典型的な使用例には、テーマ(ライト/ダーク)、ロケールと翻訳(i18n)、ユーザー認証、アプリケーション設定、および異なるネストレベルにある多くのコンポーネントが必要とするその他のグローバルデータが含まれます。Reactチームは、コンポーネントのサブツリーに対してグローバルなデータにはコンテキストを使用することを推奨していますが、アプリケーション全体に対しては推奨していません。
React Team — コンテキストドキュメント (2025)によると、コンテキストの誤った使用はReactアプリケーションにおけるパフォーマンス問題の主な原因の一つです。Providerの値が変更されるたびに、データのどの部分が変更されたかに関係なく、すべてのコンシューマーが再レンダリングされます。useMemoとコンテキスト分割による最適化がこの問題を解決します。
import { createContext, useContext } from 'react';
// デフォルト値でコンテキストを作成
const ThemeContext = createContext('ライト');
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={`btn-${theme}`}>Click</button>;
}
ReactのコンテキストメカニズムはProvider-Consumerパターンを通じて実装されています。createContextは2つのエンティティを持つオブジェクトを返します。Provider — 値を渡すコンポーネント、およびuseContextで使用されるコンテキストオブジェクト自体です。Providerはコンポーネントツリーにマウントされ、ネストの深さに関係なくすべての子要素に値を渡します。
ReactがuseContext呼び出しに遭遇すると、ファイバーツリーを走査してそのコンテキストに最も近いProviderを探します。Providerが見つかればその値が返されます。Providerが見つからなければ、createContextに渡されたデフォルト値が返されます。この検索はすべてのレンダーで発生しますが、ファイバーノードのメモ化のおかげで非常に高速であり、パフォーマンスに影響を与えません。
React — コンテキスト内部構造 (2024)によると、useContextの内部実装はuseStateと同様にフックのリンクリストを使用します。各フックはファイバーノードへの参照を保存し、ReactがどのProviderがそのコンテキストに対応するかを迅速に判断できるようにします。Providerが値を更新すると、Reactはそのコンテキストを使用するすべてのファイバーノードを再レンダリング用にマークします。
Providerコンポーネントは互いにネストして、コンテキストの階層を作成できます。各子Providerは自身のサブツリーに対して親の値をオーバーライドします。これは、ある画面でライトテーマが必要であり、ネストされたモーダルウィンドウでダークテーマが必要な場合に便利です。useContextは常にツリーの上位にある最も近いProviderの値を返します。
const UserContext = createContext(null);
const ThemeContext = createContext('ライト');
function App() {
return (
<UserContext.Provider value={{ name: 'Alice' }}>
<ThemeContext.Provider value='dark'>
<Profile />
</ThemeContext.Provider>
</UserContext.Provider>
);
}
createContext(defaultValue)関数はコンテキストオブジェクトを作成します。defaultValueパラメーターは、コンポーネントがuseContextを呼び出したが、ツリーの上位に対応するProviderがない場合に使用されます。defaultValueがない場合、useContextはundefinedを返し、予期しないエラーにつながる可能性があります。意味のあるデフォルト値またはnullを常に渡すことをお勧めします。
カスタムProviderの作成は、コンテキストロジックをカプセル化する一般的なパターンです。このようなProvider内では、状態が(useStateまたはuseReducerを介して)保存され、Providerのvalueプロップを通じて提供されます。これにより、コンシューマーコンポーネントから実装の詳細が隠され、コンテキスト管理ロジックが一箇所に集中化されます。
// 状態管理付きカスタムProvider
const AuthContext = createContext(null);
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const login = useCallback(async (email, pass) => {
const u = await loginApi(email, pass);
setUser(u);
}, []);
return (
<AuthContext.Provider value={{ user, login }}>
{children}
</AuthContext.Provider>
);
}
関数コンポーネントでは、useContextがコンテキストにアクセスする唯一の方法です。これは、render-propパターンを必要とした古いContext APIのConsumerコンポーネントを置き換えます: <ThemeContext.Consumer>{value => ...}</ThemeContext.Consumer>。useContextはコードをより直線的で読みやすくし、特に1つのコンポーネントで複数のコンテキストを扱う場合に効果的です。
1つのコンポーネントで複数のコンテキストを使用する場合、各コンテキストに対してuseContextを複数回呼び出すだけです。各呼び出しは対応するProviderの値を返します。各コンテキストは独立したエンティティであるため、呼び出しの順序は重要ではありません。Reactは同じファイバー参照システムを通じて複数の呼び出しを最適化します。
function Dashboard() {
const { user } = useContext(AuthContext);
const theme = useContext(ThemeContext);
const { locale } = useContext(I18nContext);
return (
<div className={`dashboard-${theme}`}>
<h1>{locale.greeting}, {user.name}</h1>
</div>
);
}
useContextとReduxの選択は、状態管理の規模と複雑さに依存します。useContext + useReducerは、小規模から中規模のアプリケーション向けのReduxの軽量な代替品です。外部ライブラリのインストールが不要で、学習が容易で、ほとんどのタスクに十分です。Reduxは、ミドルウェア、デベロッパーツール、不変更新を伴う厳格なアーキテクチャが必要な場合に正当化されます。
useContextに対するReduxの主な利点は、再レンダリングの最適化です。デフォルトでは、Providerの値が変更されると、すべてのコンテキストコンシューマーが再レンダリングされます。ReduxはuseSelectorとshallowEqualを使用して、コンポーネントが状態の特定の部分のみを購読できるようにし、大規模アプリケーションでの再レンダリング回数を大幅に削減します。コンテキストも多数の小さなコンテキストに分割することで最適化できます。
| 基準 | useContext | Redux |
|---|---|---|
| 複雑さ | 外部依存関係なし | ストアとミドルウェアの設定が必要 |
| 再レンダリング | 変更があるとすべてのコンシューマー | 特定のスライスを購読しているもののみ |
| DevTools | React DevTools | タイムトラベル機能付きRedux DevTools |
| ミドルウェア | 未対応 | Redux Thunk, Saga, Observable |
| 選ぶタイミング | 中規模アプリ、3–5コンテキスト | 複雑なビジネスロジックを持つ大規模アプリ |
Reduxメンテナー — Reduxを使用すべき時 (2024)によると、Reactアプリケーションの70%はReduxを必要としません。コンポーネントが50未満で、状態にキャッシュ、デバウンス、副作用を伴う複雑なロジックが含まれていない場合 — useContext + useReducerで十分以上です。Reduxはボイラープレートを追加するため、意識的に使用する必要があります。
最も一般的な間違いは、Providerのレンダリングごとにvalueオブジェクトを再作成することです。Providerにvalue={{ user, login }}を渡すと、Providerがレンダリングされるたびに新しいオブジェクトが作成され、データが変更されていなくてもすべてのコンシューマーが再レンダリングされます。解決策は、useMemoで値をメモ化するか、頻繁に変更されるデータとほとんど変更されないデータに別々のコンテキストを使用することです。
// ❌ レンダリングごとに新しいオブジェクト — すべてのコンシューマーが再レンダリング
<AuthContext.Provider value={{ user, login }}>{children}</AuthContext.Provider>
// ✅ メモ化された値 — userまたはloginが変更されたときのみ再レンダリング
const authValue = useMemo(() => ({ user, login }), [user, login]);
<AuthContext.Provider value={authValue}>{children}</AuthContext.Provider>
“大きなコンテキスト”の問題を解決するには、グローバル状態を論理グループに分割します: AuthContext、ThemeContext、I18nContext。各コンテキストは自身の領域を担当し、独立して更新されます。これはuseMemoで1つの巨大なコンテキストを最適化しようとするよりもシンプルで、より予測可能な再レンダリング動作を提供します。
よくある質問
はい、Providerのvalueにミューテーター関数を渡せば可能です。典型的なパターンは、Providerに状態を保存し、useContextを介してデータと更新関数の両方を渡すことです。子コンポーネントがこれらの関数を呼び出し、Providerの状態変更が自動的にすべてのコンシューマーを更新します。これは小規模アプリケーション向けの基本的なRedux代替です。
単純なシナリオでは、useContextはストアとミドルウェアのオーバーヘッドがないため高速です。しかし、頻繁な更新と多数のコンシューマーがある場合、Reduxのセレクター(useSelector)は状態の特定の部分のみを購読するため、Reduxが有利です。一方、useContextは変更があるとすべてのコンシューマーを再レンダリングします。更新頻度の高いアプリケーション(アニメーション、リアルタイム)には、Reduxまたは専門ライブラリを選択してください。
いいえ。useContextは、すべてのフックと同様に、React関数コンポーネントまたはカスタムフック内でのみ呼び出すことができます。通常の関数(ユーティリティやサービスなど)でコンテキスト値を取得する必要がある場合は、コンポーネントからパラメーターとして渡すか、React外部でグローバル状態を持つ別のモジュールを使用してください。
TypeScriptでのコンテキストの型付けは、createContextで型を指定することで行います: createContext<AuthContextType | null>(null)。これによりuseContext(AuthContext)が正しい型の値を返すことが保証されます。便利なパターンは、useContextを呼び出し、nullをチェックし、明確なエラーをスローするカスタムuseAuthフックを作成することです: “useAuth must be used within AuthProvider”。
最も一般的な理由は、コンシューマーコンポーネントが対応するProviderの内部にないことです。ProviderがuseContextが使用されているサブツリー全体をラップしていることを確認してください。2番目の理由 — Providerに異なるコンテキストオブジェクトが渡されています: 開発者はcreateContextを呼び出してコンテキストを作成しますが、useContextを別のcreateContextインスタンスで使用しています。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。