React Context API と useContext() の使い方

JavaScriptReact

フロントエンドフレームワーク React の Context API と useContext() の使い方についてまとめました。

ウェブ上で見つかる Context API と useContext() についての説明は、読者にクラスコンポーネントの知識があることを前提としているものが多いようですが(私が見たものがたまたまそうだっただけかもしれません)、そのような説明はクラスコンポーネント主流の時代から React を使い続けてきた人にはわかりやすいですが、今の「 Hooks + 関数コンポーネント」主流の時代に React を学ぶ人にはかえってわかりづらいのではないかと思います。

今回は「 Hooks + 関数コンポーネント」が主流の時代に React を学ぶ方(=かつての私)に向けて、クラスコンポーネントの知識を前提としない形での説明をしてみます。

対象読者

React を少しだけ知っている人。

  • useState() は理解している
  • useContext() はよく知らない
  • クラスコンポーネントもよく知らない

React Context API の主要な概念

useContext() の使い方の説明に入る前に、 React の Context API を理解する上で重要な 3 つの概念についてかんたんに説明します。

  1. Context オブジェクト
  2. Context Provider
  3. Context Consumer

1. Context オブジェクト

Context オブジェクトとは、コンポーネントツリー上直接の親子関係にない(=ツリー上離れたところにいる)コンポーネント間で同じ値を共有するための道具です。 「範囲が限定されたグローバル変数」のようなものを利用するためのものと捉えるとよいと思います。

Context オブジェクトには ProviderConsumer という 2 つのコンポーネントが備わっています。

Context.Provider
Context.Consumer

2. Context Provider

Context Provider とは、 Context オブジェクトが持つコンポーネントで、対象の値の利用可能な範囲を指定するために使うものです。 具体的には、コンポーネントツリー上で Context Provider の内側にあるすべてのコンポーネントからその Provider に対応したコンテキストの値を利用できます(具体例は後ほど示します)。

3. Context Consumer

Context Consumer とは、 Context Provider と同じく Context オブジェクトに備わったコンポーネントのひとつで、コンテキストの値を利用したい場所で使うものです。

Context Consumer を使えば、コンポーネントツリーを外に外に見ていっていちばん近くにある Context Provider の Context にひもづけられた値にアクセスすることができます(具体例は後ほど示します)。

Context API とは、 Context オブジェクトとそれに備わった ProviderConsumer 、この 3 つを提供するものです。

フックのひとつである useContext() はこの Consumer の代わりになるものです。 Consumer をそのまま使うと複雑になりがちな処理が useContext() を使うとシンプルに書けます。 今回は Consumer は使わず、この「 useContext() + Context オブジェクト + Provider コンポーネント」の組み合わせで Context API を利用する形を紹介します。

Context API と useContext() の使い方

サンプル 1. 公式ドキュメントの useContext()

記事執筆時点で React の公式ドキュメントの Context API のページ ではクラスコンポーネントを使った説明がされていますが、これを useContext() を使った形に書き換えます。

最初に、 useContext() はどのような流れで利用すればよいかについておさらいます。 useContext() は次の流れで利用します。

  1. createContext() を使って Context オブジェクトを生成する
  2. 1 で生成した Context オブジェクトの Provider をコンポーネントツリーの上方に差し込む
  3. 差し込んだ Provider の子孫コンポーネントの中で useContext() を使って Context オブジェクトにひもづけられた値を取得する

公式ドキュメントのサンプルを useContext() を使って書くと次のとおりになります。

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

// 1. `createContext()` で Context オブジェクトを生成する
const ThemeContext = createContext('light')

// 2. 1 で生成した `Context` オブジェクトの `Provider` をコンポーネントツリーの上方に差し込む
const App = () => <ThemeContext.Provider value="dark">
  <Toolbar />
</ThemeContext.Provider>

const Toolbar = (props) => <div>
  <ThemedButton />
</div>

// 3. 差し込んだ `Provider` の子孫にあたるコンポーネントで `useContext()` を使って値を取得する
const ThemedButton = () => {
  // `<ThemeContext.Provider>` に渡された `value` の値がここで利用できる
  const theme = useContext(ThemeContext)
  // 初期状態で `theme` の値は `"dark"`
  return <Button theme={theme} />
}

(上の 1 2 3 の手順に相当する部分にコメントを付けているのでコメントもあわせて読んでみてください)

このサンプルでは、 useContext() を使うことで App コンポーネントの中で定義された値( "dark" )に、その子孫コンポーネントの ThemedButton がアクセスできています。 共有したい値を引数を通してリレーすることなく値が共有できるところがポイントです。

Context API の仕組みはこれだけです。 ただこの例はちょっとシンプルすぎるので、 Context API を使うメリットがもう少しわかりやすいサンプルを見てみましょう。

サンプル 2. ページのカラーモードを切り替える機能

ページのカラーモードを切り替えられる機能を実装するのに Context API と useContext() を使用してみます。 この機能を実装するために、以下 4 つのファイルを作っていきます:

  • context/color-mode.js: カラーモードを管理するための Context を定義する
  • component/layout.js: ツリーの中で上位の方にあるコンポーネント
  • component/header.js: ツリーの中で下位の方にあるコンポーネント その 1
  • component/footer.js: ツリーの中で下位の方にあるコンポーネント その 2

カラーモードは文字列で保持することにします。 light natural dark の 3 つのモードを用意します。

context/color-mode.js:

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

const colorModes = {
  light: 'Light',
  natural: 'Natural',
  dark: 'Dark',
}

const ColorContext = createContext({
  color: '',
  setColor: () => {},
})

const ColorProvider = ({ children }) => {
  const [color, setColor] = useState('light')

  return <ColorContext.Provider value={{ color, setColor }}>
    {children}
  </ColorContext.Provider>
}

const useColorContext = () => useContext(ColorContext)

export { ColorProvider, useColorContext, colorModes }

このファイルでは 3 つのことをしています。

  1. まず createContext() を使って ColorContext という Context オブジェクトを生成しています
  2. 続いて、 ColorContextProvider を使い ColorProvider というコンポーネントを定義しています
    • ColorProvider 内では色を管理するための state を useState() を使って定義し ColorContextProvidervalue として渡しています
  3. さらに、 useContext() を使って ColorContext.Provider に渡された value を利用するための useColorContext というヘルパー関数を生成しています

道具がひととおり揃ったので、 ColorProviderColorContextcolorModes を実際のコンポーネントの中で使っていきます。

まずは ColorProvider です。

component/layout.js:

import React from "react"

import { ColorProvider } from "../context/color-mode"
import { Header } from "./header"
import { Footer } from "./footer"

const Layout = ({ children }) => {
  return <ColorProvider>
    <Header />
    { children }
    <Footer />
  </ColorProvider>
}

export default Layout

ここでは context/color-mode.js で用意した ColorProviderLayout が返すツリーのルートに差し込んでいます。

こうしておくことで、コンポーネントツリー上 Layout に含まれるコンポーネント(後ほど定義する HeaderFooter )から ColorContext.Provider に渡された value にアクセスできるようになります。

つづいて利用側の HeaderFooter を定義します。

component/header.js:

import React from "react"

import { useColorContext, colorModes } from "../context/color-mode"

const Header = () => {
  const { color, setColor } = useColorContext()
  return <header>
    <h1>◯◯商事</h1>
    <div> カラーモード: {color} </div>
    <div>
      <select
        defaultValue={color}
        onChange={(e) => setColor(e.target.value)}
      >
        {Object.entries(colorModes).map(([value, label]) =>
          <option value={value} >{label}</option>)
        }
      </select>
    </div>
  </header>
}

export default Header

ここでは context/color-mode.js で用意したヘルパー関数 useColorContext() とオブジェクト colorModes を使って、 ColorProvidervalue として渡された { color, setColor } を取り出しています。 そしてこれらと colorModes を組み合わせてカラーを変更するためのセレクトボックスを生成しています。

フッターにもヘッダーと同等のカラー変更機能を持たせてみます。

component/footer.js:

import React from "react"

import { useColorContext, colorModes } from "../context/color-mode"

const Footer = () => {
  const { color, setColor } = useColorContext()
  return <footer>
    <div> カラーモード: {color} </div>
    <div>
      <select
        defaultValue={color}
        onChange={(e) => setColor(e.target.value)}
      >
        {Object.entries(colorModes).map(([value, label]) =>
          <option value={value} >{label}</option>)
        }
      </select>
    </div>
  </footer>
}

export default Footer

Footer でやっていることは Header とほぼ同じです。 useColorContext() を使ってカラーが変更できるセレクトボックスを生成しています。

1 つめの ThemeContext のサンプルでは useContext() の利用先で値が参照できるだけでしたが、このサンプルでは HeaderFooter という 2 箇所で setColor() という関数にアクセスできるため、値の参照だけでなく変更も行うことができます。 これができると用途の幅が大幅に広がります。

ということで、以上 React の Context API と useContext() の使い方についてでした。

useContext() 単体では「引数で値をリレーしなくても離れたコンポーネント間でかんたんに値が共有できる」というメリットがあるだけですが、これにその他のフック( useState() useReducer() 等)を組み合わせることによって、複雑になりがちな処理を非常にシンプルにわかりやすく書けるメリットが生まれます。

半分は自分用のまとめですが、「 React の Context って何?」「 useContext() ってどう使えばいいの?」という人の参考になればと思います。

参考


アバター
後藤隼人 ( ごとうはやと )

Python や PHP を使ってソフトウェア開発やウェブ制作をしています。詳しくはこちら