ReactのuseMemoについて学習した。
Table of contents
Open Table of contents
useMemoとは
useMemoは、Reactのメモ化Hooksであり、依存値が変更されない限り、前回の計算結果を再利用するためのフック。 不要な計算を防ぐことで、パフォーマンス改善が期待できる。
useMemoの基本的な書き方
useMemoの基本的な書き方は以下の通り。
const value = useMemo(() => {
return 計算結果
}, [依存値]);
// 値段と個数をかけた合計値を算出する
const total = useMemo(() => {
return price * count
}, [price, count]);
useMemoの使い方
使い方①:大量のポストから必要なものをフィルタリング
postsの中からキーワードに投稿タイトルが該当するものをフィルタリングする。
useEffectを使用して、ポストの取得とpostsの更新を行う。
filteredPostsの依存配列にpostsとkeywordを指定する。
isDarkModeは、useStateを使用して、意図的に再レンダリングを発生させるために作成。
isDarkModeが切り替わっても、filteredPostsは再計算されない。
import { useState, useMemo, useEffect } from 'react';
type Posts = {
id: number;
title: string;
completed: boolean;
};
export default function UserSearchList() {
const [posts, setPosts] = useState<Posts[]>([]);
const [keyword, setKeyword] = useState('');
const [isDarkMode, setIsDarkMode] = useState(false);
useEffect(() => {
async function fetchPosts() {
try {
const res = await fetch('https://jsonplaceholder.typicode.com/todos/');
if (!res.ok) {
throw new Error(`HTTP Error: ${res.status}`);
}
const data = await res.json();
setPosts(data);
} catch(error) {
console.error(error);
}
}
fetchPosts();
}, []);
const filteredPosts = useMemo(() => {
return posts.filter((post) => {
const lowerKeyword = keyword.toLowerCase()
return post.title.toLowerCase().includes(lowerKeyword);
});
}, [posts, keyword]);
return (
<>
<button onClick={() => {
setIsDarkMode(!isDarkMode)
console.log('モード切替が実行されました');
}}>
モード切替(現在: {isDarkMode ? 'ダーク' : 'ライト'})
</button>
<div>
<input type="text" value={keyword} onChange={(e) => setKeyword(e.target.value)} />
</div>
<ul>
{filteredPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</>
);
}
使い方②:大量のユーザーデータからフィルター、ソート
LARGE_USER_DATAでサンプルデータを10000件作成し、ソート用のfilteredUsersを作成。
スコア順にソートされる。
検索ワードが変わった時に、useMemoを使用して1万件のフィルター&ソートを再計算する。
こちらも意図的に再レンダリングさせるためにisDarkModeの切替できるように設定。
import { useState, useMemo } from 'react';
// 擬似的に1万件のユーザーデータを作成
const LARGE_USER_DATA = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `User ${i}`,
score: Math.floor(Math.random() * 10000),
}));
export default function UserSearchList() {
const [searchTerm, setSearchTerm] = useState('');
const [isDarkMode, setIsDarkMode] = useState(false); // 計算とは無関係なState
// 「検索ワード」が変わった時だけ、1万件のフィルター&ソートを再計算する
const filteredUsers = useMemo(() => {
console.log('1万件の計算(フィルター&ソート)が実行されました');
return LARGE_USER_DATA.filter((user) =>
user.name.toLowerCase().includes(searchTerm.toLowerCase())
).sort((a, b) => b.score - a.score);
}, [searchTerm]); // 依存配列には searchTerm だけを指定
return (
<div style={{ background: isDarkMode ? '#333' : '#fff', color: isDarkMode ? '#fff' : '#000', padding: '20px' }}>
{/* 1. 計算とは関係ないボタン */}
<button onClick={() => {
setIsDarkMode(!isDarkMode)
console.log('モード切替が実行されました');
}}>
モード切替(現在: {isDarkMode ? 'ダーク' : 'ライト'})
</button>
{/* 2. 検索入力欄 */}
<input
type="text"
placeholder="ユーザー名を検索..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
style={{ display: 'block', margin: '10px 0' }}
/>
{/* 3. 結果の表示(上位5件) */}
<ul>
{filteredUsers.slice(0, 5).map((user) => (
<li key={user.id}>{user.name} (スコア: {user.score})</li>
))}
</ul>
</div>
);
}
注意点
useMemoが効果を発揮しやすいのは、下記のような場合です。
- 重い計算
- ソート
- フィルタ
- 集計
- 計算コストが高い処理
- 大量データの加工
const text = useMemo(() => {
return "React";
}, []);
このようなテキストを返すだけのものは使用しない。
まとめ
計算結果を再利用し、データの加工処理を効率化するための機能である。 依存配列の値が変更されない限り再計算を行わず、不要な計算を減らすことでパフォーマンス改善が期待できる。 ただ、すべての処理に使うと逆にオーバーヘッドになるため、ボトルネックを見極めて適切に導入することが大切。