【React】深い階層へのデータ共有を効率化するContext APIの基本

# はじめに

Reactではコンポーネント間で値を共有する際、propsを使うのが基本です。
しかし、ツリーの深い場所まで同じデータを渡す場合は、**バケツリレー** が発生しがちです。

このような煩雑さを解決してくれるのが、Reactの**Context API**です。

https://react.dev/learn/passing-data-deeply-with-context

https://react.dev/reference/react/useContext

本記事では、Context の概要や用途、サンプルコードなどを記載します。

# Contextとは？

> “Context lets a component receive information from a parent component without explicitly passing it through every level of the tree.”
> — [Passing Data Deeply with Context](https://react.dev/learn/passing-data-deeply-with-context)

Reactの**Context API**は、ツリー構造の深い位置にあるコンポーネントでも、親から明示的にpropsを渡さずに値を受け取れる仕組みです。

例えば、次のようにProviderで囲っておけば、どの深さの子コンポーネントからでもその値にアクセスできます。

```.tsx
<MyContext.Provider value={...}>
  <App />
</MyContext.Provider>
```

# Contextが向いている場面

Context の実際の用途としては、以下のような「アプリ全体（異なる複数階層のコンポーネント）で使われる、めったに変わらない値」に適しています。

* テーマ設定（ライト／ダーク）
* 現在の言語（i18n）
* ログインユーザー情報

:::note warn
逆に「フォームの入力内容」や「一時的なUI状態」など、頻繁に変わるデータには向いていません。Contextで扱うと、パフォーマンスの低下やコードの複雑化を招く可能性があります。
:::

# 実装サンプル

本記事では、以下のような **string型の配列を2つContextで管理**する例を紹介します。

| 名前                 | 内容                  |
| ------------------ | ------------------- |
| `fruitChoices`     | 果物の選択肢（例：りんご、バナナ）   |
| `vegetableChoices` | 野菜の選択肢（例：にんじん、ピーマン） |

:::note
あくまで Context の実装方法をわかりやすく伝えるためのサンプルです。中身はただの定数なので、実用途としては、わざわざ Context 化する必要はないです。
:::

## 開発環境
開発環境は以下の通りです。

- Windows11
- React 18.3.1
- React Router 7.5.2
- TypeScript 5.8.3
- Vite 6.3.4
- Node.js 22.13.1
- npm 11.0.0

## 1. Contextの定義とProviderの作成

```ChoiceContext.tsx
import { createContext, ReactNode, useContext } from "react";

type ChoiceContextType = {
  fruitChoices: string[];
  vegetableChoices: string[];
};

const ChoiceContext = createContext<ChoiceContextType>({
  fruitChoices: [],
  vegetableChoices: [],
});

export const ChoiceProvider = ({ children }: { children: ReactNode }) => {
  const fruitChoices = ["りんご", "みかん", "バナナ"];
  const vegetableChoices = ["にんじん", "ほうれん草", "ピーマン"];

  return (
    <ChoiceContext.Provider value={{ fruitChoices, vegetableChoices }}>
      {children}
    </ChoiceContext.Provider>
  );
};

/** Contextの値を取得するカスタムフック */
export const useChoices = () => useContext(ChoiceContext);
```

* 初期値を`createContext`にそのまま渡すことで、型をシンプルにしています（`undefined`チェックも不要）

## 2. Provider で Context で保持した状態を利用するコンポーネント全体をラップする

```App.tsx
import { Route, Routes } from "react-router-dom";
import ChoiceViewer from "./pages/ChoiceViewer";
import { ChoiceProvider } from "./contexts/ChoiceContext";
import { NotFount } from "./pages/NotFound";

function App() {
  return (
    <Routes>
      <Route
        path="/choice-viewer"
        element={
          <ChoiceProvider>
            <ChoiceViewer />
          </ChoiceProvider>
        }
      />
      <Route path="*" element={<NotFount />} />
    </Routes>
  );
}

export default App;

```


## 3. Contextを使って値を表示する

```ChoiceViewer.tsx
import { useChoices } from "./ChoiceContext";

const ChoiceViewer = () => {
  const { fruitChoices, vegetableChoices } = useChoices();

  return (
    <div>
      <h3>果物の選択肢</h3>
      <ul>
        {fruitChoices.map((fruit) => (
          <li key={fruit}>{fruit}</li>
        ))}
      </ul>

      <h3>野菜の選択肢</h3>
      <ul>
        {vegetableChoices.map((vegetable) => (
          <li key={vegetable}>{vegetable}</li>
        ))}
      </ul>
    </div>
  );
};

export default ChoiceViewer;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/e87b5e45-ac8a-4433-9dbe-1f1b1f70248e.png)

:::note warn
「2. Provider で Context で保持した状態を利用するコンポーネント全体をラップする」をしないと、Context で保持した状態は取得できません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/e0f14ad3-bd26-47b9-b551-9f95458f5364.png)

:::

# まとめ
この記事では、ReactのContext APIについて解説しました。Contextを適切に活用することで、コードの可読性と保守性を高め、「バケツリレー」問題から解放された効率的なコンポーネント設計が可能になります。

# 参考

* [Passing Data Deeply with Context - react.dev](https://react.dev/learn/passing-data-deeply-with-context)
* [useContext - react.dev](https://react.dev/reference/react/useContext)