useContext 完全入門【React】props のバケツリレーを解消する
ReactのuseContextとは何か、コンポーネントツリーを超えてデータを渡す仕組みを初心者向けに解説。createContextとProviderの使い方、テーマやログイン情報の共有パターンを紹介します。
コンポーネントが深くなるにつれて「このデータを孫コンポーネントまで渡したいのに、途中のコンポーネントを全部経由しないといけない……」という問題が起きます。これを「props のバケツリレー(Prop Drilling)」と呼びます。
useContext を使うと、コンポーネントの階層を気にせず、どこからでも必要なデータにアクセスできます。テーマ設定・ログイン情報・言語設定など、アプリ全体で共有したいデータの管理に最適です。
この記事でわかること:
- props のバケツリレー問題とは何か
- Context の作り方(
createContext)と提供(Provider) useContextでデータを受け取る方法- テーマ切り替えの実装例
- Context の使いすぎに注意するポイント
Context とは?
Context は、コンポーネントツリーを通じてデータを「テレポート」させる仕組みです。
通常のデータの流れ(props):
App → Header → Navigation → UserInfo
↓
props を一つずつ渡していく(バケツリレー)
Context を使ったデータの流れ:
App(Providerで値を提供)
↓
どのコンポーネントからでも useContext で取り出せる
使い方の3ステップ
ステップ1:createContext でコンテキストを作る
import { createContext } from "react";
// コンテキストを作成(デフォルト値を設定)
const ThemeContext = createContext("light");ステップ2:Provider で値を提供する
function App() {
const [theme, setTheme] = useState("light");
return (
// Provider の value に渡したい値を設定
<ThemeContext.Provider value={theme}>
<Header />
<Main />
</ThemeContext.Provider>
);
}ステップ3:useContext で値を受け取る
import { useContext } from "react";
function Header() {
// どれだけ深い階層でも直接取り出せる
const theme = useContext(ThemeContext);
return (
<header className={theme === "dark" ? "header-dark" : "header-light"}>
サイトヘッダー
</header>
);
}実践例:テーマ切り替え
import { createContext, useContext, useState } from "react";
// テーマ用コンテキスト(値と更新関数をまとめて提供)
const ThemeContext = createContext({
theme: "light",
toggleTheme: () => {},
});
// Provider コンポーネントとして切り出す
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
const toggleTheme = () => {
setTheme((prev) => (prev === "light" ? "dark" : "light"));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// どのコンポーネントでも使えるカスタムフック
function useTheme() {
return useContext(ThemeContext);
}
// 使用例
function ToggleButton() {
const { theme, toggleTheme } = useTheme();
return (
<button onClick={toggleTheme}>
現在のテーマ: {theme === "light" ? "ライト" : "ダーク"}
</button>
);
}
function App() {
return (
<ThemeProvider>
<ToggleButton />
</ThemeProvider>
);
}Provider は一番近いものが優先される
同じコンテキストの Provider が複数ある場合、最も近い(内側の)Provider の値が使われます。
<ThemeContext.Provider value="light">
<div>
{/* ここでは "light" */}
<ThemeContext.Provider value="dark">
<div>
{/* ここでは "dark" */}
<MyComponent /> {/* → "dark" を受け取る */}
</div>
</ThemeContext.Provider>
</div>
</ThemeContext.Provider>この特性を使って、特定のサブツリーだけ異なるテーマを適用することができます。
パフォーマンスに注意する
Context の値が変わると、そのコンテキストを使っているすべてのコンポーネントが再レンダリングされます。
// ❌ 注意:オブジェクトをそのまま渡すと毎回新しい参照になる
function App() {
const [user, setUser] = useState(null);
return (
// 毎回のレンダリングで新しいオブジェクトが作られる
<UserContext.Provider value={{ user, setUser }}>
...
</UserContext.Provider>
);
}
// ✅ useMemo でオブジェクトをキャッシュする
function App() {
const [user, setUser] = useState(null);
const value = useMemo(() => ({ user, setUser }), [user]);
return (
<UserContext.Provider value={value}>
...
</UserContext.Provider>
);
}Context を使うべきか?
Context は便利ですが、使いすぎに注意が必要です。
| 使うべき場面 | 代替手段で十分な場面 |
|---|---|
| テーマ・言語設定 | 1〜2階層のprops渡し |
| ログインユーザー情報 | コンポーネント構成の見直し |
| 複数箇所で共有するデータ | props のコンポジション |
階層が2〜3つ程度なら、props を渡す方がデータの流れが明確でシンプルです。「アプリ全体で使う値か?」を基準に判断しましょう。
まとめ
useContextは Context から値を受け取るフックcreateContextでコンテキストを作り、Providerで値を提供、useContextで受け取る- props のバケツリレー問題を解消できる
- 最も近い
Providerの値が優先される - 値が変わると使用しているすべてのコンポーネントが再レンダリングされるので、パフォーマンスに注意