【React】コンテクスト(Context)とは何か?便利なデータ共有方法を実例で解説|Provider, createContext, useContext,プロップス・ドリリング(Props Drilling)

react-prograshi(プロぐらし)-kv React.js
記事内に広告が含まれていることがあります。

アプリケーション全体でログインユーザー情報やテーマ設定を管理したいけど、毎回propsで渡すのは面倒ですよね。

Reactのコンテクスト(Context)は、まさにその悩みを解決するために存在します。コンテクスト(Context)はコンポーネントツリーの階層を無視して、データを必要な場所へ直通で届けられる便利なデータ共有方法です。

この記事では、冗長なProps Drilling(プロップス・ドリリング)を避け、アプリケーション開発の効率と保守性を格段に向上させるコンテクストについて、基本となるcreateContextやuseContextなどの使い方を実例で解説しています。

コンテクスト(Context)とは何か?

Reactのコンテクスト(Context)とは、コンポーネント間でデータ(状態)を簡単に共有するための仕組みです。

通常、Reactで親コンポーネントから子コンポーネントへデータを渡すには「props(プロパティ)」を使います。しかし、親から遠く離れた孫やひ孫コンポーネントにまでデータを渡したい場合、間にいるすべてのコンポーネントにpropsを渡していく必要があります。

そのデータを必要としていないコンポーネントにもpropsを記述しなくてはいけないためコードや管理が煩雑になります。

コンテクストを使うと、このpropsバケツリレーをせずに、コンポーネントツリー内のどこからでもデータにアクセスできるようになります。


プロップス・ドリリング(Props Drilling)とは何か?

上記でも解説した通り、コンテクストを使わない場合、Reactではデータを親コンポーネントから子コンポーネントへprops(プロパティ)を使って渡します。

深い階層のコンポーネントにデータを渡す際、途中のコンポーネントにはそのデータが必要ないにも関わらず、ひたすらpropsを渡していく必要が出てきます。

この現象を「プロップス・ドリリング(Props Drilling)」と呼びます。

コンテクストを使うと、このプロップス・ドリリングを回避し、親コンポーネントからpropsを明示的に渡さなくても、ツリー内の任意の深さにあるコンポーネントが情報(状態や設定など)を受け取れるようになります。

Point

コンテクストは、プロップス・ドリリング(Props Drilling)の回避手段です。


コンテクストの簡単なイメージと流れ

コンテクストは、アプリケーション全体で共有したいデータを「共通の箱」に入れておくイメージです。

コンテクストの簡単な流れ
  1. まずは「createContext」で保管用の箱を作成します。
  2. 「Provider」で箱の中に入れたいデータを囲みます。
  3. 「useContext」フックを使って箱の中のデータにアクセスします。


コンテクストの作り方実例

STEP1:共通の箱を作る(createContext関数)

ReactライブラリからcreateContext関数を呼び出して使います。引数は Provider がないときのデフォルト値となります。

import { createContext } from 'react';
export const 任意のコンテクスト名 = createContext(初期値);

例えば、アプリケーション全体で使う共通のデータ保管場所の名前を「AuthContext」とする場合は以下のようにします。対象のProviderが存在しないときのデフォルト値はnullとしています。

import { createContext } from 'react';

export const AuthContext = createContext(null);


コンテクストは複数作成可能

createContextを使ってコンテクスト(箱)をいくつでも作成できます。

むしろ、データの種類や用途に応じてコンテクストを分割するのが一般的です。

import { createContext } from 'react';

export const TasksContext = createContext(null);
export const TasksDispatchContext = createContext(null);

以下のように各コンポーネントファイル毎に記述することもあります。

import { createContext } from 'react';
export const AuthContext = createContext(null); // 認証情報用の箱
import { createContext } from 'react';
export const ThemeContext = createContext('light'); // テーマ設定用の箱


STEP2:Providerで共有データと共有先を指定

作成したコンテクストのProviderメソッドを使って、共有したいデータを提供します。

Providerで囲んだコンポーネントが、そのデータを利用できるようになります。

<ContextName.Provider value={共有したいデータ}>
    {/* データにアクセスできる子コンポーネント */}
</ContextName.Provider>


例えば、変数authDataを複数のコンポーネントと共有する場合は以下のように記述します。

import { useState } from 'react';
import { AuthContext } from './AuthContext';
import MainRouter from './MainRouter'; // アプリの全コンポーネント

function App() {
  const [user, setUser] = useState({ name: '田中', role: 'admin' });

  // ログインやログアウトのロジック
  const login = (userData) => setUser(userData);
  const logout = () => setUser(null);

  // ステーションから提供するデータ(value)
  const authData = { user, login, logout }; 

  return (
    // Providerで囲むことで、この中の全コンポーネントが authData にアクセス可能になる
    <AuthContext.Provider value={authData}>
      <MainRouter />
    </AuthContext.Provider>
  );
}

MainRouterコンポーネントにauthDataを渡すことができます。


例えば、MainRouter.jsは以下のようになっているとします。

// MainRouter.js
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Layout from './Layout'; // ヘッダーやサイドバーを含む共通レイアウト
import HomePage from './pages/HomePage';
import DashboardPage from './pages/DashboardPage';
import SettingsPage from './pages/SettingsPage';
import LoginPage from './pages/LoginPage';

function MainRouter() {
  return (
    // 1. ルーティング機能の提供
    <Router>
      {/* 2. 共通レイアウトコンポーネント (AuthContextデータにアクセス可能) */}
      <Layout> 
        {/* 3. パスに応じたページ表示の定義 */}
        <Routes>
          {/* 誰でもアクセスできるページ */}
          <Route path="/" element={<HomePage />} />
          <Route path="/login" element={<LoginPage />} />

          {/* 認証が必要なページ (AuthContextを使ってログイン状態をチェックする) */}
          <Route path="/dashboard" element={<DashboardPage />} />
          <Route path="/settings" element={<SettingsPage />} />
        </Routes>
      </Layout>
    </Router>
  );
}

export default MainRouter;
Point

MainRouterコンポーネント自体はauthDataを使っていません。ポイントは、Propsを記述していないにも関わらず、その中にある、DashboardPage.jsやSettingsPage.jsなどのコンポーネントの中でauthDataを使うことができるという点です。

注意点

Provideでデータを渡すコンポーネントを指定するときは、アクセスしたいコンポーネントツリー全体を包括する、最も親側のコンポーネントを一つ指定するというのが正しい使い方です。

以下のように、データを渡したいコンポーネントを一つ一つ記述する方法はNGです。

// ❌ 間違った書き方
<AuthContext.Provider value={authData}>
    <Header />
    <Sidebar /> 
    <Footer /> 
</AuthContext.Provider>


STEP3:useContextフックでデータを取り出す

コンテクストの中に入っているデータにアクセスするにはuseContextフックを使います。

import { useContext } from 'react';
import { コンテクスト名 } from './コンテクスト.js'; // Contextをインポート

const { プロパティ名 } = useContext(コンテクスト名); //欲しいデータを取得


例えば、Step2のMainRouterコンポーネントの中の、Layoutコンポーネントの中の、Routeコンポーネントの中の、DashboardPage.jsで、渡されたデータを使うには以下のようにします。

import { useContext } from 'react'; //useContextフックをインポート
import { AuthContext } from './AuthContext'; // Contextをインポート

function DashboardPage() {
  
  // 親のLayoutを経由せず、App.jsのProviderから直接データを取り出す
  const { user } = useContext(AuthContext); 

  if (!user || user.role !== 'admin') {
    return <h1>アクセス権がありません</h1>; // 管理者権限をチェック
  }

  return <h2>管理者ダッシュボード</h2>;
}


コンテクストを使うことで、間にいるLayout.jsは、自分がデータを必要としなければ、そのデータを意識する必要がまったくなくなります。

これがProps Drillingが解消される仕組みです。

タイトルとURLをコピーしました