カテゴリ: React 更新日: 2026/03/19

Props drillingとは?ReactのContext APIで解決する方法を初心者向けに解説

Props drilling問題とは?Context APIで解決できる理由
Props drilling問題とは?Context APIで解決できる理由

先生と生徒の会話形式で理解しよう

生徒

「Reactでデータを渡すときに、propsをいくつもコンポーネントに渡していたら、だんだんコードがごちゃごちゃしてきました。これって普通のことですか?」

先生

「それはProps drillingという問題です。コンポーネントの階層が深くなるほど起きやすく、多くの初心者が悩むポイントです。」

生徒

「どうすれば解決できますか?」

先生

「Context APIを使えばスッキリ解決できます。実際のコードを見ながら、一緒に確認していきましょう!」

1. Props drillingとは何か?問題の本質を理解しよう

1. Props drillingとは何か?問題の本質を理解しよう
1. Props drillingとは何か?問題の本質を理解しよう

ReactでWebアプリを作るとき、画面の部品を「コンポーネント」という単位に分けて作成します。コンポーネント同士でデータを受け渡すときに使うのがpropsです。propsとは、親コンポーネントから子コンポーネントへデータを渡すための仕組みのことです。

ところが、アプリが大きくなってコンポーネントの入れ子が深くなると問題が起きます。たとえば、一番上の親コンポーネントにあるデータを、4階層や5階層下の子コンポーネントで使いたい場合、途中のコンポーネントがそのデータをまったく使わなくても、ただ下に渡すためだけにpropsを受け取って次へ渡し続けなければなりません。

この状態をProps drilling(プロップスドリリング)と呼びます。drillとは「穴を掘る」という意味で、まるで地面を深く掘り進めるようにpropsを渡し続けることからこの名前がついています。コードが複雑になり、修正や管理がとても大変になるため、Reactを学ぶうえで必ず理解しておくべき課題です。

2. Props drillingの問題をコードで確認してみよう

2. Props drillingの問題をコードで確認してみよう
2. Props drillingの問題をコードで確認してみよう

実際にProps drillingがどういう状態かを、シンプルなコードで確認してみましょう。親コンポーネントのユーザー名を、途中のコンポーネントを経由して孫コンポーネントまで渡す例です。


import React from "react";

function App() {
  const userName = "山田さん";
  return <Parent userName={userName} />;
}

function Parent({ userName }) {
  // ParentはuserNameを使わないが、下に渡すだけのために受け取る
  return <Child userName={userName} />;
}

function Child({ userName }) {
  // ChildもuserNameを使わないが、下に渡すだけのために受け取る
  return <GrandChild userName={userName} />;
}

function GrandChild({ userName }) {
  // ようやくここで使う
  return <p>ログイン中:{userName}</p>;
}

export default App;
画面に「ログイン中:山田さん」と表示されます。ただし、ParentとChildはuserNameをまったく使っていないのに、受け取って渡すだけのためにpropsを書かなければなりません。

このように、途中のコンポーネントが「ただの通り道」になってしまうのがProps drillingの問題です。コンポーネントの数が増えるほど、このような無駄な受け渡しが増えていきます。データの変更があったとき、途中のすべてのコンポーネントを修正しなければならないため、バグの原因にもなりやすいです。

3. Props drillingが引き起こす具体的なデメリット

3. Props drillingが引き起こす具体的なデメリット
3. Props drillingが引き起こす具体的なデメリット

Props drillingがなぜ問題視されるのか、具体的なデメリットを整理しておきましょう。

まず、コードの見通しが悪くなります。どのコンポーネントがどのデータを本当に必要としているのかが分かりにくくなります。ファイルを見ただけでは「このpropsはどこから来てどこへ行くのか」を追うだけで時間がかかります。

次に、修正コストが大きくなります。たとえばデータの名前を変更したい場合、途中のすべてのコンポーネントを修正する必要があります。変更漏れがあればエラーになるため、テストや確認の手間も増えます。

また、コンポーネントの再利用がしにくくなります。本来コンポーネントは独立した部品として別の場所でも使い回せるのが理想ですが、propsの受け渡しが前提になっているコンポーネントは他の場所で使いにくくなります。

これらの問題を根本から解決するのが、ReactのContext APIです。

4. Context APIがProps drillingを解決できる理由

4. Context APIがProps drillingを解決できる理由
4. Context APIがProps drillingを解決できる理由

Context APIは、コンポーネントの階層に関係なく、どこからでもデータを直接取り出せる仕組みを提供します。イメージとしては、建物の中に引かれた共有の水道管のようなものです。各部屋(コンポーネント)は、隣の部屋を経由しなくても、蛇口(useContext)をひねるだけで水(データ)を使えます。

Context APIを使うときの流れは次の3ステップです。1. createContextでContextを作る、2. Providerでデータを提供する範囲を囲む、3. useContextで必要なコンポーネントがデータを取り出す。この3ステップを押さえれば、Props drillingの問題をきれいに解消できます。

特に「アプリ全体で使うデータ」、たとえばログイン中のユーザー情報、テーマカラー、選択中の言語などは、Context APIで管理するのがとても向いています。

5. Context APIを使ってProps drillingを解消してみよう

5. Context APIを使ってProps drillingを解消してみよう
5. Context APIを使ってProps drillingを解消してみよう

先ほどのProps drillingのコードを、Context APIを使って書き直してみましょう。ParentとChildはuserNameを受け取る必要がなくなります。


import React, { createContext, useContext } from "react";

// Contextを作成する
const UserContext = createContext("");

function App() {
  const userName = "山田さん";

  return (
    // Providerでアプリ全体を囲み、valueにデータをセット
    <UserContext.Provider value={userName}>
      <Parent />
    </UserContext.Provider>
  );
}

function Parent() {
  // userNameは不要なのでpropsを受け取らなくてよい
  return <Child />;
}

function Child() {
  // こちらも同様
  return <GrandChild />;
}

function GrandChild() {
  // useContextで直接データを取り出す
  const userName = useContext(UserContext);
  return <p>ログイン中:{userName}</p>;
}

export default App;
画面に「ログイン中:山田さん」と表示されます。ParentとChildには一切propsを渡していませんが、GrandChildはuserNameを正しく取得できています。

コードがスッキリしたのが分かります。途中のコンポーネントはuserNameのことを知らなくてよくなり、それぞれが自分の役割だけに集中できるようになりました。これがContext APIの大きなメリットです。

6. Contextファイルを分けて管理する実践的な書き方

6. Contextファイルを分けて管理する実践的な書き方
6. Contextファイルを分けて管理する実践的な書き方

実際の開発では、Contextの定義を専用のファイルに切り出すのが一般的なやり方です。こうすることでコードが整理されて、チームでの開発や後からの修正もしやすくなります。ここではショッピングカートのアイテム数を管理する例で見てみましょう。


// CartContext.jsx
import { createContext, useState } from "react";

export const CartContext = createContext(null);

export function CartProvider({ children }) {
  const [cartCount, setCartCount] = useState(0);

  const addToCart = () => {
    setCartCount((prev) => prev + 1);
  };

  return (
    <CartContext.Provider value={{ cartCount, addToCart }}>
      {children}
    </CartContext.Provider>
  );
}

次に、このProviderをAppで読み込み、カートのボタンコンポーネントでデータを使います。


// App.jsx
import React, { useContext } from "react";
import { CartProvider, CartContext } from "./CartContext";

function CartButton() {
  const { cartCount, addToCart } = useContext(CartContext);

  return (
    <div>
      <p>カートの中身:{cartCount}個</p>
      <button onClick={addToCart}>カートに追加する</button>
    </div>
  );
}

function App() {
  return (
    <CartProvider>
      <h1>オンラインショップ</h1>
      <CartButton />
    </CartProvider>
  );
}

export default App;
画面に「カートの中身:0個」と表示され、「カートに追加する」ボタンを押すたびに数が増えていきます。CartButtonはCartProviderの子孫であれば、どこに配置してもカートの情報を取得・更新できます。

このようにProviderコンポーネントを専用ファイルで作っておくと、アプリのどこからでもuseContextひとつで状態を読み書きできるようになります。

7. Props drillingとContext APIの使い分けポイント

7. Props drillingとContext APIの使い分けポイント
7. Props drillingとContext APIの使い分けポイント

Context APIはとても便利な機能ですが、すべての場面で使うのが正解というわけではありません。適切に使い分けることが、きれいなコードを書くうえで重要です。

まず、コンポーネントの階層が2〜3段程度であれば、通常のpropsで十分です。Contextを使うと逆にコードの読み方が複雑になることがあります。シンプルな親子関係のデータ受け渡しには、propsのほうが直感的で分かりやすいです。

一方で、次のようなケースではContext APIが向いています。アプリ全体のテーマ設定(ダークモード・ライトモードなど)、ログイン中のユーザー情報、多言語対応の言語設定、グローバルな通知やアラートの状態管理などが代表的な例です。

また、Contextの値が頻繁に変わるデータには注意が必要です。Contextの値が変わると、そのContextを参照しているすべてのコンポーネントが再描画されます。再描画(再レンダリング)とは、画面を更新するためにコンポーネントが再計算されることで、頻繁に起きるとアプリの動作が重くなる場合があります。1秒に何度も変わるようなデータはContextには向かないため、用途をしっかり選ぶことが大切です。

8. Props drillingを防ぐためのコンポーネント設計の考え方

8. Props drillingを防ぐためのコンポーネント設計の考え方
8. Props drillingを防ぐためのコンポーネント設計の考え方

Props drillingはContext APIで解決できますが、そもそもコンポーネントの設計を工夫することでも問題を起きにくくすることができます。

コンポーネントの分割粒度を見直すことが大切です。コンポーネントを細かく分けすぎると階層が深くなり、Props drillingが起きやすくなります。本当に分ける必要があるかを考えながら設計するとよいです。

また、データを使うコンポーネントのできるだけ近くで管理するという考え方も重要です。Reactには「データはそれを使うコンポーネントの近くに置く」という設計の原則があります。アプリ全体で使わないデータをわざわざ一番上の親コンポーネントで管理すると、不必要にpropsを渡す距離が長くなります。

Context APIとprops、どちらを使うかの判断基準は「どれだけ多くのコンポーネントで使うか」と「データはどれだけ変わるか」の2点を軸に考えると整理しやすいです。この考え方を身につけておくと、Reactのコード設計がぐっと上達します。

カテゴリの一覧へ
新着記事
New1
React
JSXとJavaScript式の組み合わせ方を完全解説!初心者でもわかる中括弧の使い方
New2
React
ReactのContext.Providerでデータを渡す仕組みを完全解説!初心者でもわかるProviderの使い方
New3
React
ReactのState更新が非同期で行われる理由と注意点をやさしく解説!初心者でもわかるStateの扱い方
New4
React
PropsとStateの違いを徹底解説!使い分けのポイントまとめ
人気記事
No.1
Java&Spring記事人気No1
React
ReactとTypeScriptの環境構築をやさしく解説!Viteとtsconfigの設定も丁寧に紹介
No.2
Java&Spring記事人気No2
React
Reactでフォーカスイベントを制御する方法!onFocusとonBlurを初心者向けに解説
No.3
Java&Spring記事人気No3
React
ReactのJSXとJavaScriptの違いを完全ガイド!初心者でもわかるReactのJSX入門
No.4
Java&Spring記事人気No4
React
コンポーネントの作り方!基本的な書き方と使い方をReact初心者向けに解説
No.5
Java&Spring記事人気No5
React
Reactとは?初心者でもわかるReact.jsの基本概念と特徴をやさしく解説
No.6
Java&Spring記事人気No6
React
Reactでキーボードイベントを活用する方法!onKeyDown, onKeyUp, onKeyPressを初心者向けに解説
No.7
Java&Spring記事人気No7
React
Reactはフレームワーク?ライブラリ?Reactの正しい位置づけと役割を初心者向けに解説!
No.8
Java&Spring記事人気No8
Next.js
Next.jsのRoute Groupの使い方を完全ガイド!App Routerでフォルダ構成を整理する方法