React Context API と useContext() の使い方
フロントエンドフレームワーク React の Context API と useContext()
の使い方についてまとめました。
ウェブ上で見つかる Context API と useContext()
についての説明は、読者にクラスコンポーネントの知識があることを前提としているものが多いようですが(私が見たものがたまたまそうだっただけかもしれません)、そのような説明はクラスコンポーネント主流の時代から React を使い続けてきた人にはわかりやすいですが、今の「 Hooks + 関数コンポーネント」主流の時代に React を学ぶ人にはかえってわかりづらいのではないかと思います。
今回は「 Hooks + 関数コンポーネント」が主流の時代に React を学ぶ方(=かつての私)に向けて、クラスコンポーネントの知識を前提としない形での説明をしてみます。
対象読者
React を少しだけ知っている人。
useState()
は理解しているuseContext()
はよく知らない- クラスコンポーネントもよく知らない
React Context API の主要な概念
useContext()
の使い方の説明に入る前に、 React の Context API を理解する上で重要な 3 つの概念についてかんたんに説明します。
- Context オブジェクト
- Context Provider
- Context Consumer
1. Context オブジェクト
Context
オブジェクトとは、コンポーネントツリー上直接の親子関係にない(=ツリー上離れたところにいる)コンポーネント間で同じ値を共有するための道具です。
「範囲が限定されたグローバル変数」のようなものを利用するためのものと捉えるとよいと思います。
各 Context
オブジェクトには Provider
と Consumer
という 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
オブジェクトとそれに備わった Provider
と Consumer
、この 3 つを提供するものです。
フックのひとつである useContext()
はこの Consumer
の代わりになるものです。
Consumer
をそのまま使うと複雑になりがちな処理が useContext()
を使うとシンプルに書けます。
今回は Consumer
は使わず、この「 useContext()
+ Context
オブジェクト + Provider
コンポーネント」の組み合わせで Context API を利用する形を紹介します。
Context API と useContext()
の使い方
サンプル 1. 公式ドキュメントの useContext()
版
記事執筆時点で React の公式ドキュメントの Context API のページ ではクラスコンポーネントを使った説明がされていますが、これを useContext()
を使った形に書き換えます。
最初に、 useContext()
はどのような流れで利用すればよいかについておさらいます。
useContext()
は次の流れで利用します。
createContext()
を使ってContext
オブジェクトを生成する- 1 で生成した
Context
オブジェクトのProvider
をコンポーネントツリーの上方に差し込む - 差し込んだ
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
: ツリーの中で下位の方にあるコンポーネント その 1component/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 つのことをしています。
- まず
createContext()
を使ってColorContext
というContext
オブジェクトを生成しています - 続いて、
ColorContext
のProvider
を使いColorProvider
というコンポーネントを定義していますColorProvider
内では色を管理するための state をuseState()
を使って定義しColorContext
のProvider
にvalue
として渡しています
- さらに、
useContext()
を使ってColorContext.Provider
に渡されたvalue
を利用するためのuseColorContext
というヘルパー関数を生成しています
道具がひととおり揃ったので、 ColorProvider
・ ColorContext
・ colorModes
を実際のコンポーネントの中で使っていきます。
まずは 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
で用意した ColorProvider
を Layout
が返すツリーのルートに差し込んでいます。
こうしておくことで、コンポーネントツリー上 Layout
に含まれるコンポーネント(後ほど定義する Header
や Footer
)から ColorContext.Provider
に渡された value
にアクセスできるようになります。
つづいて利用側の Header
と Footer
を定義します。
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
を使って、 ColorProvider
に value
として渡された { 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()
の利用先で値が参照できるだけでしたが、このサンプルでは Header
と Footer
という 2 箇所で setColor()
という関数にアクセスできるため、値の参照だけでなく変更も行うことができます。
これができると用途の幅が大幅に広がります。
ということで、以上 React の Context API と useContext()
の使い方についてでした。
useContext()
単体では「引数で値をリレーしなくても離れたコンポーネント間でかんたんに値が共有できる」というメリットがあるだけですが、これにその他のフック( useState()
useReducer()
等)を組み合わせることによって、複雑になりがちな処理を非常にシンプルにわかりやすく書けるメリットが生まれます。
半分は自分用のまとめですが、「 React の Context って何?」「 useContext()
ってどう使えばいいの?」という人の参考になればと思います。