【React初心者向け】React.memoで不要な再レンダリングを防ぐ!実践的なパフォーマンス最適化方法を解説
こんにちは、とまだです。
Reactでアプリを作っていて、「なんだか動作が重いな...」と感じたことはありませんか?
私も最初の頃は、コンポーネントがやたら再レンダリングされて、画面の動きがもっさりしてしまう経験がありました。特にリスト表示で100件とか表示していると、1件更新しただけで全部描き直されて「なんで?」って思ったものです。
今回は、そんな悩みを解決してくれる React.memo について、実際の使用例とともにわかりやすく解説します!
React.memoってなに?
React.memoを簡単に説明すると、「変更がない部分は再描画をスキップしてくれる機能」です。
例えば、大きなオフィスの会議室を想像してみてください。 会議の途中で新しい資料が配られたとき、全員が席を立ち上がって資料を取りに行く必要はありませんよね。必要な人だけが動けばいいはずです。
React.memoも同じで、propsに変更がなければ前回の結果を再利用してくれるんです。つまり、「変更がないなら無駄に動く必要なし!」ということです。
基本的な使い方
React.memoの使い方はとってもシンプルです。
import React from "react";
const DisplayText = React.memo(function DisplayText({ message }) {
console.log("DisplayText rendered");
return <p>{message}</p>;
});
export default DisplayText;
コンポーネントを React.memo()
でラップするだけで、propsが同じ値なら再レンダリングをスキップしてくれます。
めちゃくちゃ簡単ですよね!
いつReact.memoを使うべき?
React.memoが特に効果を発揮するのは、大量のデータを扱ったり、頻繁に更新が起きる画面です。
リスト表示での活用
ショッピングサイトの商品一覧を想像してみてください。 100個の商品があって、そのうち1個だけ在庫が変わったとします。
React.memoを使わない場合、全ての商品カードが再レンダリングされてしまいます。これは明らかに無駄ですよね。
// React.memoを使わない場合(非効率)
function ProductCard({ product }) {
console.log(`商品 ${product.id} がレンダリングされました`);
return (
<div>
<h3>{product.name}</h3>
<p>価格: {product.price}円</p>
<p>在庫: {product.stock}個</p>
</div>
);
}
// React.memoを使った場合(効率的)
const ProductCard = React.memo(function ProductCard({ product }) {
console.log(`商品 ${product.id} がレンダリングされました`);
return (
<div>
<h3>{product.name}</h3>
<p>価格: {product.price}円</p>
<p>在庫: {product.stock}個</p>
</div>
);
});
React.memoを使えば、在庫が変わった商品だけが再レンダリングされるようになります。
私も以前、ECサイトの商品一覧で同じような問題に遭遇して、React.memoを導入したら劇的に動作が軽くなった経験があります。
ダッシュボード画面での活用
管理画面のダッシュボードも良い例です。
リアルタイムで更新される売上グラフと、ほとんど変わらないユーザー情報パネルがあるとします。ユーザー情報パネルをReact.memoでラップしておけば、売上データが更新されてもユーザー情報は再レンダリングされません。
これで「なんでユーザー情報まで再描画されるの?」という無駄をなくせます。
React.memoの注意点
React.memoは便利ですが、使う際に気をつけたいポイントがあります。
オブジェクトや配列の問題
以下のコードを見てください。
function App() {
const [count, setCount] = useState(0);
// 毎回新しいオブジェクトが作られる
const user = { name: "田中", age: 25 };
return (
<div>
<button onClick={() => setCount(count + 1)}>
カウント: {count}
</button>
<UserInfo user={user} />
</div>
);
}
const UserInfo = React.memo(function UserInfo({ user }) {
console.log("UserInfo がレンダリングされました");
return <p>{user.name}さん({user.age}歳)</p>;
});
このコードだと、React.memoを使っても毎回 UserInfo
が再レンダリングされてしまいます。
なぜかというと、user
オブジェクトが毎回新しく作られているからです。見た目は同じでも、JavaScriptでは「別のもの」として扱われてしまうんです。
これは私も最初にハマったポイントで、「React.memo使ってるのに効いてない!」って悩みました。
解決策:useMemoを組み合わせる
この問題は useMemo
を使って解決できます。
function App() {
const [count, setCount] = useState(0);
// useMemoで値を固定
const user = useMemo(() => ({
name: "田中",
age: 25
}), []); // 空の依存配列なので、一度だけ作成される
return (
<div>
<button onClick={() => setCount(count + 1)}>
カウント: {count}
</button>
<UserInfo user={user} />
</div>
);
}
これで、UserInfo
は本当に必要な時だけ再レンダリングされるようになります。
関数を渡す場合の注意点
関数をpropsで渡す場合も同じ問題が起きます。
function App() {
const [count, setCount] = useState(0);
// 毎回新しい関数が作られる
const handleClick = () => {
console.log("ボタンがクリックされました");
};
return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount(count + 1)}>増加</button>
<ChildButton onClick={handleClick} />
</div>
);
}
const ChildButton = React.memo(function ChildButton({ onClick }) {
console.log("ChildButton がレンダリングされました");
return <button onClick={onClick}>子ボタン</button>;
});
この場合は useCallback
を使って関数を固定します。
function App() {
const [count, setCount] = useState(0);
// useCallbackで関数を固定
const handleClick = useCallback(() => {
console.log("ボタンがクリックされました");
}, []); // 空の依存配列なので、関数は一度だけ作成される
return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount(count + 1)}>増加</button>
<ChildButton onClick={handleClick} />
</div>
);
}
ただし、useCallback
と useMemo
を使いすぎると、逆にパフォーマンスが悪くなることもあります。本当に必要な場所だけで使いましょう。
実践的な使い分けのコツ
私の経験から、React.memoを使うべき場面とそうでない場面をまとめました。
使うべき場面
大量のリストアイテムを表示する商品一覧やユーザー一覧では効果抜群です。
重い計算を含むコンポーネントのグラフやチャートでも威力を発揮します。
頻繁に更新される親の子コンポーネントで、リアルタイム更新画面の一部などにも有効です。
使わなくても良い場面
シンプルなコンポーネントのボタンやテキスト表示では、そもそもレンダリングコストが低いので効果が薄いです。
必ず毎回変わるコンポーネントの現在時刻表示などでは、メモ化しても意味がありません。
親と一緒に更新されるコンポーネントのフォームの入力項目なども、一緒に更新されるのが自然なので不要です。
実際の改善例
最後に、実際にReact.memoでパフォーマンスが改善された例を見てみましょう。
// 改善前:全てのTodoアイテムが毎回再レンダリング
function TodoApp() {
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState("");
const addTodo = () => {
setTodos([...todos, { id: Date.now(), text: newTodo, done: false }]);
setNewTodo("");
};
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
));
};
return (
<div>
<input
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
/>
<button onClick={addTodo}>追加</button>
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} onToggle={toggleTodo} />
))}
</div>
);
}
// 改善後:変更されたTodoアイテムだけ再レンダリング
const TodoItem = React.memo(function TodoItem({ todo, onToggle }) {
console.log(`Todo ${todo.id} がレンダリングされました`);
const handleToggle = useCallback(() => {
onToggle(todo.id);
}, [todo.id, onToggle]);
return (
<div>
<input
type="checkbox"
checked={todo.done}
onChange={handleToggle}
/>
<span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
{todo.text}
</span>
</div>
);
});
この改善により、100個のTodoアイテムがあっても、1個だけチェックした時に他の99個は再レンダリングされなくなります。
まとめ
React.memoは、不要な再レンダリングを防いでパフォーマンスを改善してくれる便利な機能です。
特に大量のデータを扱うリストや、頻繁に更新される画面で効果を発揮します。
ただし、オブジェクトや関数をpropsで渡す場合は、useMemo
や useCallback
と組み合わせて使う必要があります。
全てのコンポーネントに適用するのではなく、本当に必要な場所を見極めて使うことが大切ですね。
もし「アプリの動作が重いかも」と感じることがあれば、ぜひReact.memoを試してみてください!きっと違いを実感できるはずです。