おい、調子はどうだい?
最近、後輩から「親と子でどうやってデータを同期させればいいのか分からなくなりました」「`useState`を使ってるのに値がズレるんです」っていう相談をやたらと受けるんだよね。
君も経験があるかもしれない。コンポーネントが肥大化してきて、「あ、このボタンのクリック状態、隣のパネルでも使いたいじゃん!」ってなったとき、安易にグローバルな状態管理(ReduxとかZustandとか)を持ち出したり、無理やり子から親へデータを逆流させようとしてコードがスパゲッティ化する現象……。
あれ、本当にやめよう。大体のケースは、Reactの基本中の基本である「状態の引き上げ(Lifting State Up)」を正しく理解して適用するだけで綺麗に解決するんだ。
今回は、実務の現場で「お、こいつのコード、分かってるな」と思われるような、状態の引き上げの極意を叩き込んでやるよ。コーヒーでも飲みながら、じっくり聞いてくれ。
—
なぜ「状態の引き上げ」が必要なのか?(Reactのデータフローの鉄則)
まず大前提として、Reactは「単方向データフロー(Unidirectional Data Flow)」で動いている。
データは常に「親から子へ」流れる。これは絶対のルールだ。
よくある初心者の間違いが、「子コンポーネント側で保持している状態を、別の兄弟コンポーネントから直接参照したい」というもの。おいおい、Reactのコンポーネントは独立した関数だぞ。兄弟同士が直接会話することはできない。
ここで必要になるのが、「共有したい状態を、それらの共通の祖先(親)コンポーネントに持たせる(引き上げる)」というアプローチだ。
ブラウザの裏側で何が起きているか?
状態を親に引き上げると、何が嬉しいのか?
Reactの裏側では、親の`useState`が更新されると、その親コンポーネント全体が再評価(Re-render)され、それに伴って子コンポーネント群へ新しいプロパティ(Props)が流し込まれる。
つまり、ブラウザのDOMツリーの整合性が常に保たれ、「画面の表示と内部データのズレ(不整合)」が構造的に起きなくなるんだ。これが、Reactの宣言的UIの真骨頂だよ。
—
現場で即戦力になる!「検索フィルター付きリスト」の実装パターン
百聞は一見に如かず。実務でよくある「検索入力フォーム(子A)」と「その結果を表示するリスト(子B)」のコンポーネントを例に取ろう。
検索キーワードという「状態」は、フォームが持ってちゃダメだ。リスト側もそのキーワードを知る必要があるからね。だから、共通の親に引き上げる。
以下のコードをそのまま見てくれ。実務でそのまま使えるように、意図や注意点をコメントにたっぷり込めておいた。
import React, { useState } from ‘react’;
// ==========================================
// 1. 子コンポーネント:検索入力フォーム
// ==========================================
type SearchBarProps = {
keyword: string;
onKeywordChange: (newKeyword: string; // 親から渡された状態と、それを更新する関数を受け取るだけ
) => void;
};
const SearchBar: React.FC
return (
onKeywordChange(e.target.value)}
placeholder=”名前を入力してください…”
style={{ padding: ‘8px’, width: ‘300px’, borderRadius: ‘4px’, border: ‘1px solid #ccc’ }}
/>
);
};
// ==========================================
// 2. 子コンポーネント:リスト表示
// ==========================================
type UserListProps = {
users: string[];
};
const UserList: React.FC
return (
-
{users.length === 0 ? (
- 該当するユーザーはいません。
{user}
) : (
users.map((user, index) => (
))
)}
);
};
// ==========================================
// 3. 親コンポーネント:状態の所有者(Container)
// ==========================================
const ALL_USERS = [
‘田中 太郎’,
‘佐藤 花子’,
‘鈴木 一郎’,
‘高橋 恵子’,
‘渡辺 健太’,
];
export const UserManagementContainer: React.FC = () => {
// ★ここに「検索キーワード」という状態を引き上げる(Single Source of Truth)
const [keyword, setKeyword] = useState
// 状態から派生するデータ(Derived State)のフィルタリング処理
// ※実務ではuseMemoを使うべきケースだが、今回はシンプルに保つために直叩き
const filteredUsers = ALL_USERS.filter((user) =>
user.toLowerCase().includes(keyword.toLowerCase())
);
return (
ユーザー管理ダッシュボード
「状態の引き上げ」により、入力フォームとリスト間でデータを同期しています。
{/ 状態と、状態を更新するセッター関数をPropsとして子に渡す /}
{/ フィルタリング済みのデータを子に渡す /}
);
};
export default UserManagementContainer;
—
シニアが教える、実務でハマりがちな「アンチパターン」と対策
この「状態の引き上げ」を雑に実装すると、現場で痛い目を見るポイントがいくつかある。先輩からのアドバイスとして、心に刻んでおいてほしい。
1. 「バケツリレー地獄(Prop Drilling)」の弊害
親から子、子から孫、孫からひ孫へと、使ってもいないPropsをただ渡すために延々と経由させるコードを見たことないかい?
あれは「バケツリレー地獄」と呼ばれ、保守性をドブに捨てる行為だ。
対策: 引き上げる階層が深くなりすぎた(3階層以上が目安)場合は、潔く `React.Context` を使うか、Zustandなどの軽量な状態管理ライブラリへの移行を検討しよう。状態の引き上げは、あくまで「近親者間」の同期に最も威力を発揮する。
2. 不要な再レンダリングの発生源になる
親コンポーネントで `useState` を持っているため、その状態が更新されると、たとえ関係のない子コンポーネントであっても、親が持っている以上は再評価の対象になる。
対策: パフォーマンスがボトルネックになっていると感じたら、子コンポーネントを `React.memo` でラップするか、コンポーネントのツリー構造そのものを見直して、状態を持つ親をできるだけ末端(必要なツリーの最小単位)に近づけよう。
—
まとめ:基本に立ち返れ
新しい技術や流行りのライブラリをキャッチアップするのも大事だけど、結局のところ、Reactというライブラリの根幹を支えているのは、こうした「状態をどこに置くべきか(State Placement)」の設計センスなんだ。
コードが複雑になってきたとき、「あ、このデータは誰と誰が共有したいんだっけ?じゃあ親はどこだ?」と立ち返る。これができるようになれば、君の書くコードの品質は一段も二段も跳ね上がるはずだ。
さて、理論はこれくらいにして、実際に自分のプロジェクトのエディタを開いて、あちこちに散らばっている`useState`を整理してみないか?
何か詰まったら、いつでも声をかけてくれ。一緒に最高のコードベースを作っていこうぜ!

コメント