React Context APIの分割方法を解説!パフォーマンスを高めるベストプラクティス
生徒
「Reactのコンテキストを使ってみましたが、一つの箱に全部のデータを入れると、関係ない部品まで動き出して重くなるって本当ですか?」
先生
「その通りです。大きすぎる箱は効率が悪くなるので、役割ごとに箱を分けるのがベストプラクティスとされています。」
生徒
「箱を分ける…?どうやって使い分ければいいのか、具体的に教えてください!」
先生
「コンテキストを適切に分割することで、無駄な再描画を防ぎ、保守性の高いコードが書けるようになります。実際の作り方を見ていきましょう!」
1. なぜReactのコンテキストを分割する必要があるのか?
Reactの Context API(コンテキストエーピーアイ)は、複数の部品で共有したいデータを管理する「共通の倉庫」のようなものです。非常に便利ですが、初心者が陥りやすい罠があります。それは、ユーザー情報、画面の色設定、買い物かごの中身など、種類の違うすべてのデータを一つの大きな倉庫に詰め込んでしまうことです。
Reactには「コンテキストの値が一つでも変わると、そのコンテキストを参照している部品がすべて描き直される(レンダリングされる)」というルールがあります。そのため、画面の色を変えただけなのに、全く関係のないユーザー名の表示部品まで描き直されてしまいます。これを防ぐために、役割ごとに倉庫を分ける「分割」が必要になるのです。
2. コンテキストを分けるメリットと「関心の分離」
コンテキストを分割することを、プログラミングの世界では「関心の分離」と呼びます。これは「似ていない役割のものは、別々に管理する」という考え方です。例えば、料理をするときに冷蔵庫の中に服や本が入っていたら使いにくいですよね。それと同じで、プログラムも「ユーザーに関するもの」と「アプリの設定に関するもの」を分けることで、格段に扱いやすくなります。
分割することのメリットは主に二つあります。一つは「パフォーマンスの向上」です。必要なデータだけが更新されるので、パソコンやスマートフォンの動作が軽くなります。もう一つは「管理のしやすさ」です。不具合が起きたときに、どの倉庫を確認すれば良いのかがすぐに見つかるようになります。
3. 役割別に複数のコンテキストを作成する方法
それでは、具体的に二つのコンテキスト(ユーザー情報とテーマ設定)を別々に作成するコードを見てみましょう。createContext(クリエイトコンテキスト)という命令を使って、必要な数だけ倉庫の定義を作ります。
import React, { createContext, useState } from "react";
// 1. ユーザー情報のための倉庫を定義
export const UserContext = createContext();
// 2. テーマ(色)設定のための倉庫を定義
export const ThemeContext = createContext();
export function AppProviders({ children }) {
const [user, setUser] = useState({ name: "たろう" });
const [isDark, setIsDark] = useState(false);
return (
<UserContext.Provider value={{ user, setUser }}>
<ThemeContext.Provider value={{ isDark, setIsDark }}>
{children}
</ThemeContext.Provider>
</UserContext.Provider>
);
}
4. プロバイダーをネスト(重ね書き)する構成
先ほどのコードでは、二つの Provider(プロバイダー)を重ねて書いていました。プロバイダーとは、倉庫のデータを配る役割を持つ部品のことです。このように重ねて書くことを「ネストする」と言います。
この構成にすることで、内側にある children(チルドレン:アプリ全体の部品)は、外側にあるすべての倉庫にアクセスできるようになります。パソコンを触ったことがない方でも、マトリョーシカ人形のように、大きな箱の中に小さな箱が入っていて、一番内側からはすべての箱の中身が見える様子をイメージすると分かりやすいでしょう。
5. 特定のコンテキストだけを呼び出す部品の作り方
倉庫が分かれているので、使う側も必要なものだけを選んで受け取ることができます。useContext(ユーズコンテキスト)を使って、どの倉庫からデータを取り出すかを指定します。これにより、無駄な再描画が発生しない効率的な部品になります。
import React, { useContext } from "react";
import { ThemeContext } from "./App";
function ColorToggle() {
// テーマの倉庫から、現在の設定と変更用の関数だけを取り出します
const { isDark, setIsDark } = useContext(ThemeContext);
return (
<button onClick={() => setIsDark(!isDark)}>
モード切替: {isDark ? "ダーク" : "ライト"}
</button>
);
}
6. 状態と更新用関数を別々のコンテキストにする技
さらに高度なベストプラクティスとして、「現在の値」と「値を変更する命令(関数)」さえも別のコンテキストに分ける手法があります。これは、データの変更命令だけを出したい部品が、データの変化自体に反応して描き直されるのを防ぐためです。
例えば、今の数値を見たい部品と、リセットボタンだけの部品を分けるような場合です。リセットボタンは「リセットせよ」という命令を送るだけでよく、今の数値がいくらであるかを知る必要はありません。これを分けることで、真のパフォーマンス最適化が実現します。
import React, { createContext, useState, useContext } from "react";
const StateContext = createContext();
const DispatchContext = createContext();
export function CounterProvider({ children }) {
const [count, setCount] = useState(0);
return (
<StateContext.Provider value={count}>
<DispatchContext.Provider value={setCount}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}
7. コンテキスト分割を整理する「カスタムフック」
コンテキストの数が増えてくると、使う側の部品で何度も useContext を書くのが大変になります。そこで、独自の便利な命令セットである「カスタムフック」を作っておくのがプロの技です。
カスタムフックを作ることで、倉庫の名前を意識せずに「ユーザー情報をください」とお願いするだけでデータが手に入るようになります。これにより、将来的にプログラムの構造が変わったとしても、修正する場所を最小限に抑えることができます。
import { useContext } from "react";
import { UserContext } from "./App";
// 独自の便利な命令(カスタムフック)
export function useUser() {
const context = useContext(UserContext);
if (context === undefined) {
throw new Error("UserProviderの中で使ってください");
}
return context;
}
8. 分割されたコンテキストをまとめるコンポーネント
たくさんのプロバイダーが重なると、メインのプログラムが見づらくなってしまいます。ベストプラクティスでは、これらを一つにまとめた「ラッパー部品」を作ります。これにより、アプリの入り口を綺麗に保つことができます。
プログラミング未経験の方は、たくさんの延長コードを一つの電源タップにまとめるイメージを持ってください。スッキリと整理された環境は、バグ(間違い)を減らす第一歩になります。今回の分割テクニックを活用して、サクサクと快適に動くReactアプリを目指しましょう!