useContext 完全入門【React】props のバケツリレーを解消する

ReactのuseContextとは何か、コンポーネントツリーを超えてデータを渡す仕組みを初心者向けに解説。createContextとProviderの使い方、テーマやログイン情報の共有パターンを紹介します。

#react#hooks#usecontext#javascript#frontend

コンポーネントが深くなるにつれて「このデータを孫コンポーネントまで渡したいのに、途中のコンポーネントを全部経由しないといけない……」という問題が起きます。これを「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 の値が優先される
  • 値が変わると使用しているすべてのコンポーネントが再レンダリングされるので、パフォーマンスに注意

関連記事