こんにちは!Reactの学習、楽しく進められていますか?
画面上のボタンをクリックしたら別の場所がパッと変わったり、自分だけのアプリが形になっていくのは本当にワクワクしますよね。
でも、Reactを学び始めて少し経つと、こんな「モヤモヤ」にぶつかりませんか?
「あれ……? 親から子へデータを渡すために、間にいるコンポーネントにもとりあえずPropsを渡さなきゃいけないの? これ、めちゃくちゃ面倒くさくない……?」
そう、これが世のエンジニアたちが頭を抱える「Propsのバケツリレー」という現象です。
今回は、この果てしないバケツリレーをスマートに解決してくれる魔法の道具「Context(コンテキスト)」について、そして「Contextを使ったはいいけれど、なぜかアプリが重くなる……」という落とし穴を華麗に避けるコツを、一緒に紐解いていきましょう!
大丈夫、難しいことはひとつもありません。身近な例えからゆっくり見ていきましょうね。
—
1. Propsのバケツリレーって、なに?
まずは敵を知ることから始めましょう。
Reactでは、データ(お肉や野菜などの「中身」だと思ってください)は基本的に「親から子へ」の一方通行でしか渡せません。
例えば、アプリの一番てっぺん(大元)にある「ユーザーのログイン情報」を、一番深いところにある「プロフィール画像コンポーネント」で使いたいとします。
このとき、途中にいる「ヘッダー」や「メニュー」というコンポーネントは、そのログイン情報を一切使わないのに……
- 「はい、次の人へ!」
- 「はい、次の人へ!」
と、まるで運動会のバケツリレーのように、ただ運ぶだけのためにPropsを受け取って渡して……を繰り返さなければいけません。これがバケツリレーです。コンポーネントが増えれば増えるほど、コードを書くのも追うのも大変になっていきます。
そこで登場するのが「Context」という直通パイプ
「途中の人たちを全員無視して、必要な人に直接データを届けられたらいいのに!」
それを叶えてくれるのが、Reactの機能である Context です。
イメージとしては、オフィスビルに引かれた「内線電話」や、建物全体に音楽を流す「館内放送システム」のようなもの。
大元(プロバイダー)から「今のログイン情報はこれです!」と放送しておけば、途中の階(コンポーネント)をすっ飛ばして、必要な人だけがその情報を受け取ることができます。これで面倒なバケツリレーとはお別れです!
—
2. でもちょっと待って!Contextには「隠れコスト」があるんです
「わあ、じゃあ何でもかんでもContextに入れちゃえば解決じゃん!」
そう思ったあなた、ちょっと待ってください。ここからが現場のシビアなお話です。
実は、Contextには「それが変わると、そこにぶら下がっている全員が強制的に再レンダリング(画面の再描画)されちゃう」という性質があります。
分かりやすく例えてみましょう。
あなたは大きなカフェで、カウンターの電光掲示板を見ながらコーヒーの出来上がりを待っています。その電光掲示板には、以下の2つの情報が表示されています。
1. いま何組目の人が呼ばれているか(番号)
2. 今日のおすすめケーキ(情報)
ここで、「いま何組目か」が変わったので、掲示板の番号が「3組」から「4組」に更新されました。
このとき、もし「番号」と「今日のおすすめケーキ」を1つの大きなContext(お盆)にまとめて置いていたらどうなるでしょう?
番号が「4組」に変わった瞬間、カフェにいる全員が「えっ、何か変わった!?」と一斉に電光掲示板を二度見することになりますよね。たとえ、あなたが「今日のおすすめケーキ」しか見ていなくても、です。
Reactの世界でもこれと同じことが起きます。
関係ない情報が変わっただけなのに、親切心でContextに入れたせいで、関係のない子コンポーネントまで何度も再描画されてしまい、アプリの動きがカクついてしまう……これが、Contextを使うときによくある落とし穴なんです。
—
3. 解決策:データを「お皿」ごとに分けて、メモ化しよう!
じゃあどうすればいいの? という話ですよね。
答えはとてもシンプルです。「頻繁に変わるもの」と「ほとんど変わらないもの」でお皿を分ける、そして「余計な再描画をしないように覚える(メモ化する)」ことです。
百聞は一見にしかず。実際のコードを見てみましょう。
ここでは、「ユーザー名(ほとんど変わらない)」と「テーマカラー(ダークモード切替などでたまに変わる)」を管理するアプリを例にします。
実用的なサンプルコード
import React, { useState, useContext, createContext, useMemo } from ‘react’;
// 1. データを分けるために、2つのContext(直通パイプ)を作ります
// お皿A:ユーザー情報用
const UserContext = createContext(null);
// お皿B:テーマ設定用
const ThemeContext = createContext(null);
export function App() {
const [userName, setUserName] = useState(‘さくら’);
const [theme, setTheme] = useState(‘light’);
// 【超重要】useMemoを使って、値が実際に変わったときだけ新しいオブジェクトを作る
// これにより、関係のない再レンダリングを防ぎます
const userValue = useMemo(() => ({ userName, setUserName }), [userName]);
const themeValue = useMemo(() => ({ theme, setTheme }), [theme]);
return (
// 親コンポーネントでそれぞれのContextを包み込みます
React Context 最適化サンプル
);
}
// 途中のコンポーネント(Propsを一切受け渡していない点に注目!)
function MainContent() {
return (
ここは途中のコンポーネントです(Propsのバケツリレーはありません!)
);
}
// ユーザー名を表示・変更するコンポーネント
function UserProfile() {
// UserContextだけを読み込む
const { userName, setUserName } = useContext(UserContext);
console.log(‘UserProfile が再レンダリングされました!’);
return (
こんにちは、{userName}さん!
);
}
// テーマを切り替えるコンポーネント
function ThemeToggler() {
// ThemeContextだけを読み込む
const { theme, setTheme } = useContext(ThemeContext);
console.log(‘ThemeToggler が再レンダリングされました!’);
return (
現在のテーマ: {theme}
);
}
コードのポイント解説
1. Contextを分ける (`UserContext` と `ThemeContext`)
ユーザー名を変えてもテーマのContextには影響しないため、テーマ側を使っているコンポーネントが無駄に再描画されるのを防げます。
2. `useMemo` による値のメモ化
Reactでは、コンポーネントが再描画されるたびに中の変数が新しく作り直されます。そのため、何も対策しないと「中身は同じなのに別のものと判定されて再描画が走る」という悲しい現象が起きます。`useMemo` を使うことで、「中身が変わっていなかったら、前と同じものを使い回すよ!」とReactにお願いすることができます。
コンソール(F12キーを押して確認できます)を開きながら「名前を変える」ボタンを押してみてください。「UserProfile」だけが再描画され、「ThemeToggler」は無駄に再描画されていないことが確認できるはずです!
—
4. 最後に:完璧を目指さなくて大丈夫です
ここまで、Contextの最適化について少し踏み込んだお話をしました。
「うわ、なんか難しそう……`useMemo`とか毎回書かないといけないの?」と不安になったかもしれませんが、安心してください。
実際の現場でも、アプリの規模が小さいうちはここまで厳密に最適化する必要はありません。最初は素直にContextを使って、「あ、なんか画面の動きがカクついてきたな?」と感じたり、アプリが大きくなってパフォーマンスが気になり始めたタイミングで、今回お話した「データの分割」や「`useMemo`」を思い出していただければ十万馬力です!
一歩ずつ、ご自身のペースでReactの引き出しを増やしていきましょうね。
あなたのコーディングライフを、心から応援しています!

コメント