【実務・中級編】 派生状態(Derived State)の計算とmemo化 – React実践ガイド

やあ、お疲れ様。最近、コードレビューをしていて「おっ、またやってるな」と気になったことがあるんだ。
君たちも、既存のstateから計算できる値をわざわざ別のstateとして持たせて、`useEffect`で同期を取ろうとしたり、更新関数のあとに「あれ、なんで値が古いままなんだ?」と首を傾げたりした経験、ないかい?

中級に差し掛かったエンジニアが一番ハマりやすい罠、それが「派生状態(Derived State)の管理ミスと、不要な状態肥大化のアンチパターン」だ。

今日は、Reactのレンダリングの仕組みの裏側まで潜り込みながら、このモヤモヤを綺麗に解消する「派生状態の計算とmemo化の極意」を授けよう。実務でそのまま使えるコードを用意したから、コーヒー片手に最後までついてきてほしい。

—

1. なぜ「すべての値をstateにする」と地獄を見るのか?

Reactを触り始めた頃は、「画面に出したいデータはとりあえず全部 `useState` にブチ込めばいいや」となりがちだ。例えば、ユーザーの「名(firstName)」と「姓(lastName)」があるとする。ここで「フルネーム(fullName)」を表示したいとき、君ならどうする?

// ❌ やりがちなアンチパターン
const [firstName, setFirstName] = useState(‘太郎’);
const [lastName, setStateLastName] = useState(‘山田’);
const [fullName, setFullName] = useState(‘山田 太郎’); // ← これが諸悪の根源!

// 名前の変更時にわざわざfullNameも一緒に更新している…
const handleFirstNameChange = (newFirstName) => {
setFirstName(newFirstName);
setFullName(`${lastName} ${newFirstName}`); // 忘れがち&バグの温床
}

おいおい、ちょっと待ってくれ。`fullName` は、`firstName` と `lastName` さえあればいつでもその場で算出できる値だよね?
これをわざわざ独立したstateとして保持すると何が起きるか。

1. 二重管理の呪い: 片方の更新を忘れた瞬間、UIと内部データの整合性が崩れる(バグの温床)。
2. 無駄な再レンダリング: 状態が分かれていることで、Reactのレンダリングサイクルが複雑化する。

Reactの鉄則、それは 「Single Source of Truth(信頼できる唯一の情報源)」 だ。算出可能な値はstateに持つな。これが大原則なんだよ。

—

2. ブラウザの裏側で何が起きているか?(Reactの再レンダリングの真実)

「でも先生、毎回コンポーネントが再レンダリングされるたびに計算処理が走るってことは、パフォーマンスが悪くなるんじゃないですか?」

鋭いね。その疑問が出てくるあたり、君もかなり実務のパフォーマンスを意識できるようになってきた証拠だ。

ブラウザのメインスレッドの動きを想像してみよう。Reactが状態の変更を検知すると、仮想DOM(Virtual DOM)を再構築し、実際のDOMとの差分(Diffing)を計算してパッチを当てる。この一連のプロセスの中で、ただの文字列結合や簡単な配列のフィルタリング程度のJavaScriptの計算コストなんて、CPUからすれば「瞬きする間」ほどの負荷でしかない。

むしろ、不要な `useState` を増やして状態の同期ズレをデバッグする無駄な時間や、`useEffect` の依存配列地獄にハマるコストの方が、100倍重い。

ただし! その計算が「重い処理(数万件のデータのフィルタリング、複雑なグラフデータの集計など)」である場合は話が別だ。そこで登場するのが、今回の主役である `useMemo` によるメモ化のテクニックだ。

—

3. 実践!派生状態の計算と `useMemo` による最適化

百聞は一見に如かず。実務でそのまま使える、検索機能付きのユーザーリストコンポーネントを書いた。
「検索キーワード」と「フィルター条件」という元ネタ(state)から、実際に画面に表示する「絞り込み済みのリスト」という派生状態をどう美しく扱うか、コードの隅々まで見てほしい。

import React, { useState, useMemo } from ‘react’;

// ユーザーの型定義
type User = {
id: number;
name: string;
role: ‘admin’ | ‘editor’ | ‘viewer’;
isActive: boolean;
};

// ダミーの初期データ(実際の現場ではAPIから取得する想定)
const INITIAL_USERS: User[] = [
{ id: 1, name: ‘山田 太郎’, role: ‘admin’, isActive: true },
{ id: 2, name: ‘佐藤 花子’, role: ‘editor’, isActive: false },
{ id: 3, name: ‘鈴木 一郎’, role: ‘viewer’, isActive: true },
{ id: 4, name: ‘高橋 恵子’, role: ‘editor’, isActive: true },
];

export const UserManagement: React.FC = () => {
// — 1. 信頼できる情報源(State)の定義 —
const [users] = useState(INITIAL_USERS);
const [searchKeyword, setSearchKeyword] = useState(”);
const [selectedRole, setSelectedRole] = useState(‘all’);

// テーマ切り替え用の無関係なstate(これが更新されても、派生状態の計算を走らせたくない!)
const [isDarkMode, setIsDarkMode] = useState(false);

// — 2. 派生状態の計算と useMemo による最適化 —
// users, searchKeyword, selectedRole が変化した時だけ再計算する
// isDarkMode が変化しても、この重いフィルタリング処理はスキップされる!
const filteredUsers = useMemo(() => {
console.log(‘🔄 ユーザーのフィルタリング計算を実行中…’); // パフォーマンス確認用

return users.filter((user) => {
// 検索キーワードのチェック(大文字小文字を区別しない)
const matchesKeyword = user.name.toLowerCase().includes(searchKeyword.toLowerCase());

// ロール(権限)のチェック
const matchesRole = selectedRole === ‘all’ || user.role === selectedRole;

return matchesKeyword && matchesRole;
});
}, [users, searchKeyword, selectedRole]); // ← 依存配列の管理が命

// — 3. さらに派生する統計データ(これもuseMemoでスッキリ) —
const activeUserCount = useMemo(() => {
console.log(‘📊 アクティブユーザー数を集計中…’);
return filteredUsers.filter((user) => user.isActive).length;
}, [filteredUsers]);

return (

ユーザー管理ダッシュボード

{/ テーマ切り替えボタン(無関係なstateの更新) /}

{/ 検索・フィルターコントロール /}

setSearchKeyword(e.target.value)}
/>

{/ 統計情報の表示 /}

表示中のユーザー数: {filteredUsers.length} 人(うちアクティブ: {activeUserCount} 人)

{/ ユーザーリストの描画 /}

    {filteredUsers.map((user) => (

  • {user.name} ({user.role}) – {user.isActive ? ‘🟢 有効’ : ‘🔴 無効’}
  • ))}

);
};

—

4. シニアから後輩へ送る、実装時の重要な心構え

このコードを見て、「なるほど、全部 `useMemo` で囲めば完璧だな!」と思ったそこの君、ちょっと待ってくれ。それが罠なんだよ。

⚠️ `useMemo` を乱用してはいけない理由

JavaScriptのエンジンにとって、単純な変数の代入や小規模な配列メソッド(`.map()` や `.filter()` で要素数が数十件程度)を毎回実行するコストは、`useMemo` のメモ化機構(依存配列の比較処理など)のオーバーヘッドよりもはるかに軽いことが多い。

何でもかんでも `useMemo` で包むと、コードの可読性が落ちるだけでなく、逆にメモリを消費してパフォーマンスが劣化することすらある。

【シニアの判断基準】
1. 素の計算で十分なケース: 単純な文字列結合、数値の四則演算、数件〜数十件の配列操作。そのままレンダリング関数内で計算しちゃいなよ。
2. `useMemo` を使うべきケース:

  • 計算コストが明らかに高い(数千件のデータ処理、重いアルゴリズム)。
  • その派生状態(配列やオブジェクト)を、さらに他のコンポーネントへ `props` として渡したり、`useEffect` の依存配列に含めたりする場合(参照の同一性を保つ必要があるとき)。

—

まとめ

今日覚えて帰ってほしいのは、たったこれだけだ。

1. 元ネタ(state)から算出できる値は絶対に新しい state にするな。その場で計算しろ。
2. パフォーマンスのボトルネックが明確な場合、あるいは参照の安定性が必要な場合にのみ `useMemo` を使え。
3. 依存配列(dependencies array)の管理は嘘をつくな。

これができるようになるだけで、君の書くReactコードの品質は一段も二段も跳ね上がる。バグの少ない、メンテナンス性の高い美しいフロントエンドを一緒に作っていこうぜ。

さて、次のタスクに取り掛かろうか。何か質問があったらいつでも声をかけてくれよな!

コメント

タイトルとURLをコピーしました