【入門編】 useMemoとuseEffectの連携 – React実践ガイド

こんにちは!フロントエンドの現場を渡り歩いているチーフアーキテクトの私です。

Reactを触り始めて少し経つと、みんなが一度はぶつかる「壁」がありますよね。そう、`useEffect` と `useMemo` です。「あれ、画面が無限に再レンダリングされてフリーズしちゃった…」「なんか毎回ログが大量に出るんだけどなんで!?」なんて夜を過ごした方も多いはず。大丈夫、それ、みんな最初は通る道ですから安心してください。

今日は、そんな初心者泣かせな「useMemoとuseEffectの連携」について、お買い物のシチュエーションに例えながら、とことん優しく解きほぐしていきたいと思います。

—

なぜこの2つがセットで語られるのか?

Reactのコンポーネントは、状態(state)が変わるたびに「再レンダリング」というお化粧直しを何度も行います。

ここで `useEffect` を使うと、「お化粧直しが終わったあとに、これをやっておいてね」という指示(副作用)を出せます。このとき、指示のタイミングをコントロールするのが「依存配列(`[]` の中身)」ですよね。

ここで問題になるのが、「オブジェクトや配列を依存配列に入れたとき」なんです。

身近な例え:お買い物の計算メモ

ここに、「買うもののリストと合計金額」をメモした紙(オブジェクト)があるとします。

  • Aくん: 書いてある内容は全く同じだけど、毎回新しくノートをビリッと破って、そこに清書し直したメモを渡してくる人。
  • Bくん: 内容が変わらないときは、ずっと同じお気に入りのメモ帳を使い回す人。

JavaScriptの世界では、見た目の数字が同じ `[1, 2, 3]` であっても、コンポーネントが再レンダリングされるたびに「Aくん方式(新しく作られた別の紙)」として扱われてしまいます。

そのため、`useEffect` は「あれ?さっきと違う紙(新しい参照)が渡されたぞ!中身は同じっぽっちけど、念のためもう一回イベントを動かさなきゃ!」と勘違いしてしまい、予期せぬ無限ループや無駄な処理を引き起こしてしまうのです。

ここで登場するのが `useMemo` です。「Bくん方式」になって、「中身が変わってないなら、同じメモをずっと使い回してね(参照の安定化)」とお願いするための道具なんです。

—

実際のコードで見てみましょう

百聞は一見に如かず。よくある「フィルター機能付きのユーザー一覧」を例に見てみましょう。
以下のコードは、そのままコピペして動かせる形にしています。

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

export default function UserListApp() {
const [searchKeyword, setSearchKeyword] = useState(”);
const [count, setCount] = useState(0);

// ダミーのユーザー一覧データ
const rawUsers = [
{ id: 1, name: ‘たろう’, role: ‘admin’ },
{ id: 2, name: ‘はなこ’, role: ‘user’ },
{ id: 3, name: ‘けんた’, role: ‘user’ },
];

// 1. 【useMemoの出番!】
// 検索キーワードが変わった時だけ、フィルターされたリストを新しく作る。
// それ以外の時(例えば「カウントボタン」を押しただけの時)は、前回のリストを使い回す!
const filteredUsers = useMemo(() => {
console.log(‘✨ おおっと、filteredUsersの計算が走りました!’);
return rawUsers.filter((user) =>
user.name.includes(searchKeyword)
);
}, [searchKeyword]); // searchKeywordが変わらない限り、この中身は再計算されません

// 2. 【useEffectの出番!】
// フィルターされたユーザーリスト(filteredUsers)を監視して、変更があったら何かをする。
useEffect(() => {
console.log(‘🔄 useEffectが動きました! フィルター結果の人数:’, filteredUsers.length);

// ここでAPIを叩いたり、アナリティクスに送信したりする処理が入るイメージです。

}, [filteredUsers]); // ここに filteredUsers を入れています!

return (

useMemo × useEffect 連携デモ

{/ 検索キーワードを入力するインプット /}

setSearchKeyword(e.target.value)}
/>

{/ 関係ない状態(カウント)を増やすボタン /}

現在のカウント: {count}

{/ ユーザー一覧の表示 /}

結果一覧:

    {filteredUsers.map((user) => (

  • {user.name} ({user.role})
  • ))}

);
}

このコードの動きのポイント

1. 「関係ないボタン」を押したとき

  • `count` が変わるので、コンポーネント自体は再レンダリングされます。
  • でも、`useMemo` のおかげで `filteredUsers` は「中身も参照も同じだね」とキープされるため、再計算されません。
  • 結果として、`useEffect` も「お、依存している `filteredUsers` は変わってないな」と判断し、ムダに実行されません。 (コンソールに余計なログが出ないはずです!)

2. 「検索キーワード」を変更したとき

  • `searchKeyword` が変わるので、`useMemo` が新しくリストを計算します。
  • 参照が変わるため、`useEffect` が「おっ、リストが更新されたぞ!」と気づいて、しっかりと中身の処理を実行してくれます。

—

チーフアーキテクトからのアドバイス

「何でもかんでも `useMemo` で囲めば速くなるんでしょ?」と思われがちですが、実はそれもちょっと違います。`useMemo` 自体も計算コストがかかるので、今回のケースのように「オブジェクトや配列を `useEffect` の依存配列にどうしても入れたい時」にこそ、真価を発揮します。

「あ、今のバグ、もしかして毎回参照が変わって `useEffect` が暴走してただけじゃん!」と気づけるようになると、Reactを書くのが一気に楽しく、そして誇らしくなりますよ。

もし動かしていて「あれ、うまくいかないな…」と思ったら、まずは `console.log` を仕込んで、何が何回レンダリングされているのかを追っかけてみてくださいね。地道なデバッグこそが、最強のエンジニアへの一番の近道です。

それでは、快適なReactライフを!

コメント

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