useCallback 完全入門【React】関数のキャッシュで再レンダリングを最適化
ReactのuseCallbackとは何か、関数をキャッシュする仕組みと使いどころを初心者向けに解説。useMemoとの違いや、memoコンポーネントとの組み合わせ方も紹介します。
「子コンポーネントに関数をpropsとして渡しているのに、毎回再レンダリングが走ってしまう……」
そんな悩みの解決策として useCallback があります。関数コンポーネントでは、レンダリングのたびに関数が新しく作られます。これが不要な再レンダリングの原因になることがあります。
この記事でわかること:
useCallbackが必要な理由- 基本的な書き方とパラメータ
memoコンポーネントとの組み合わせパターンuseMemoとの違い- 使いすぎを避けるためのガイドライン
なぜ関数のキャッシュが必要なのか?
Reactコンポーネントは、レンダリングのたびに関数を新しく作り直します。
function Parent() {
const [count, setCount] = useState(0);
// ← count が変わるたびに新しい handleClick が作られる
const handleClick = () => {
console.log("クリック!");
};
return <Child onClick={handleClick} />;
}handleClick は毎回「別の関数」として扱われます。もし Child が memo でラップされていても、onClick の参照が変わるため毎回再レンダリングされてしまいます。
useCallback の基本的な使い方
const cachedFn = useCallback(fn, dependencies);| パラメータ | 説明 |
|---|---|
fn | キャッシュしたい関数 |
dependencies | 再生成のトリガーとなる値の配列 |
import { useCallback, useState } from "react";
function Parent() {
const [count, setCount] = useState(0);
const [theme, setTheme] = useState("light");
// count が変わっても handleClick の参照が変わらない
const handleClick = useCallback(() => {
console.log("クリック!");
}, []); // 依存なし → 一度だけ作成
return (
<>
<button onClick={() => setCount(count + 1)}>カウント: {count}</button>
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
テーマ切替
</button>
<Child onClick={handleClick} />
</>
);
}memo コンポーネントとの組み合わせ
useCallback が最も効果を発揮するのは、memo でラップした子コンポーネントに関数を渡す場合です。
import { memo, useCallback, useState } from "react";
// memo でラップ:propsが変わらなければ再レンダリングしない
const SearchResults = memo(function SearchResults({ onItemClick }) {
console.log("SearchResults レンダリング");
return (
<ul>
<li onClick={() => onItemClick("item1")}>アイテム1</li>
<li onClick={() => onItemClick("item2")}>アイテム2</li>
</ul>
);
});
function SearchPage() {
const [query, setQuery] = useState("");
const [selectedItem, setSelectedItem] = useState(null);
// useCallback なしだと query が変わるたびに SearchResults も再レンダリングされる
const handleItemClick = useCallback((item) => {
setSelectedItem(item);
}, []); // 依存なし
return (
<>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="検索..."
/>
<SearchResults onItemClick={handleItemClick} />
{selectedItem && <p>選択: {selectedItem}</p>}
</>
);
}エフェクト内で使う関数のキャッシュ
function ChatRoom({ roomId }) {
const [message, setMessage] = useState("");
// この関数を useCallback でラップすることで
// roomId が変わったときだけ再作成される
const createConnection = useCallback(() => {
return connectToRoom(roomId);
}, [roomId]);
useEffect(() => {
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createConnection]); // createConnection が変わったときだけ再実行
}useMemo との違い
// useCallback:関数そのものをキャッシュ
const handleClick = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// useMemo:関数の実行結果をキャッシュ
const result = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
// useCallback(fn, deps) は useMemo(() => fn, deps) と同等| フック | キャッシュするもの |
|---|---|
useCallback | 関数(定義) |
useMemo | 値(計算結果) |
使いすぎに注意
useCallback をすべての関数に使う必要はありません。
// ❌ 過剰:子コンポーネントに渡さない関数には不要
const handleChange = useCallback((e) => {
setInput(e.target.value);
}, []);
// ✅ シンプルに書けばよい
const handleChange = (e) => {
setInput(e.target.value);
};効果があるのは、memo でラップした子コンポーネントへ渡す場合や、useEffect の依存配列に含める関数です。
まずコードをシンプルに書き、パフォーマンス問題が確認されてから最適化するのが正しいアプローチです。
まとめ
useCallbackは関数の定義をキャッシュして、不要な関数の再作成を防ぐフックmemoコンポーネントに関数を渡す場合に効果を発揮するuseMemoの関数版(実行結果ではなく関数自体をキャッシュ)- カスタムフックが返す関数はラップすることを推奨
- パフォーマンス問題が実際に起きてから使うのがベスト
PR
useCallback 公式ドキュメントで詳細を確認しよう
useCallbackのインタラクティブなサンプルや、最適化のタイミングに関するガイドラインは公式ドキュメントで確認できます。
useCallback 公式ドキュメントを見る →