useCallbackとは?再レンダリングを防ぐための基本と使い方をわかりやすく解説

1. useCallbackが必要となる背景
Reactの関数コンポーネントは、ステートやプロップスが更新されるたびに再レンダリングが発生します。 その際、関数内部で定義されたコールバック関数も毎回新しく生成されます。
このような仕組みにより、親から関数を props 経由で渡された子コンポーネントは、関数の参照が毎回変わるため、React.memoなどでメモ化しても、毎回再レンダリングされてしまうことがあります。
tsxfunction Parent() {
const handleClick = () => {
console.log('clicked');
};
return <Child onClick={handleClick} />;
}
このようなコードでは、Parentが再レンダリングされるたびに handleClick
が新しくなり、Childの再レンダリングを引き起こしてしまいます。
2. useCallbackの基本的な使い方
tsxconst memoizedCallback = useCallback(() => {
// 実行される処理
}, [依存配列]);
useCallback
は、依存配列の中身が変わらない限り、同じ関数インスタンスを返します。
これにより、関数の参照が固定され、React.memoやuseEffectなどの依存関係として扱う際に非常に有効です。
例えば、次のように setCount
を使ってカウントアップする処理を useCallback
でメモ化できます。
tsxconst increment = useCallback(() => {
setCount((prev) => prev + 1);
}, []);
依存配列が空なので、increment
は初回のマウント時に一度だけ定義され、以降のレンダリングでも同じ関数が再利用されます。
3. React.memoとの併用とメリット
React.memoは、propsが変更されない限り再レンダリングを抑えるHOC(高階コンポーネント)です。 しかし、関数が毎回新しく渡されると、それだけで変更と見なされてしまいます。
以下のような例を見てみましょう。
tsxconst Button = memo(({ onClick }: { onClick: () => void }) => {
console.log('Button rendered');
return <button onClick={onClick}>Click Me</button>;
});
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
return (
<>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<Button onClick={handleClick} />
</>
);
}
このように、useCallback
を使えば、handleClick
の参照は維持され、Button
コンポーネントの不要な再レンダリングが防げます。
4. 実践ユースケース
フォームのバリデーション関数
tsxfunction Form() {
const [value, setValue] = useState('');
const validate = useCallback((input: string) => {
return input.length >= 5;
}, []);
return (
<div>
<input
value={value}
onChange={(e) => setValue(e.target.value)}
style={{ borderColor: validate(value) ? 'green' : 'red' }}
/>
</div>
);
}
毎回 validate
を新しく生成しないことで、バリデーションロジックを他の副作用から切り離すことができます。
リスト描画時のクリックハンドラ
tsxconst ListItem = memo(({ id, onSelect }: { id: number; onSelect: (id: number) => void }) => {
console.log('ListItem rendered:', id);
return <li onClick={() => onSelect(id)}>Item {id}</li>;
});
function List({ items }: { items: number[] }) {
const handleSelect = useCallback((id: number) => {
alert(`Selected item: ${id}`);
}, []);
return (
<ul>
{items.map((item) => (
<ListItem key={item} id={item} onSelect={handleSelect} />
))}
</ul>
);
}
このようなリストでは、ハンドラの参照が変わらなければ、個々の ListItem
は無駄にレンダリングされることがなくなります。
Context.Provider の value に関数を含める
tsxconst AppContext = createContext<{ onAction: () => void }>({ onAction: () => {} });
function AppProvider({ children }: { children: ReactNode }) {
const onAction = useCallback(() => {
console.log('context action');
}, []);
const contextValue = useMemo(() => ({ onAction }), [onAction]);
return <AppContext.Provider value={contextValue}>{children}</AppContext.Provider>;
}
useCallback → useMemo の順でメモ化することで、Providerのvalueが変わらず、消費側コンポーネントの再レンダリングも抑制できます。
5. カスタムフックによる抽象化
複数の場所で useCallback
を使っていると、ロジックが散らかりやすくなります。
そうした場合は、共通の処理をカスタムフックに抽出して再利用性を高めましょう。
tsxfunction useIncrement(setter: React.Dispatch<React.SetStateAction<number>>) {
return useCallback(() => setter((prev) => prev + 1), [setter]);
}
function Counter() {
const [count, setCount] = useState(0);
const increment = useIncrement(setCount);
return <button onClick={increment}>カウント: {count}</button>;
}
6. よくある疑問と回答
疑問 | 回答 |
---|---|
setStateは依存配列に入れる? | 基本は不要(安定しているため) |
関数の処理が速くなる? | 処理自体は変わらず、参照の維持が目的です |
useCallbackの使いすぎは? | パフォーマンス改善どころか逆に複雑化します |
7. テストへの活用
React Hooksのテストでは、関数の参照が変わらないことを確認するケースもあります。
useCallback
を使っていると、hook単体のテストが書きやすくなります。
tsxtest('useIncrementは常に同じ関数を返す', () => {
const { result, rerender } = renderHook(() => useIncrement(jest.fn()));
const first = result.current;
rerender();
const second = result.current;
expect(first).toBe(second);
});
8. useRefなど他のフックとの連携
useCallback
単体では最新の状態を参照することができません。
非同期処理やイベントリスナなどと併用する場合は useRef
を組み合わせると良いです。
tsxfunction useStableCallback(callback: () => void) {
const ref = useRef(callback);
useEffect(() => {
ref.current = callback;
}, [callback]);
return useCallback(() => ref.current(), []);
}
9. useMemoとの違い
比較項目 | useCallback | useMemo |
---|---|---|
主目的 | 関数のメモ化 | 値のメモ化 |
返り値 | 関数 | 任意の値 |
使用例 | propsに渡すコールバック関数 | 高コスト計算の結果の再利用 |
10. まとめと使いどころチェック
- 関数を props に渡す場面では useCallback を使う
- React.memo や useEffect などと連携して副作用の最小化
- useRef や useMemo との併用も考慮
- 冗長な useCallback の使用は避け、必要な場面だけ使う
正しく使えば、Reactアプリのパフォーマンスと保守性は大きく向上します。
11. 公式リソースリンク
12. 最後に
React開発では「不要な再レンダリングを防ぐ」ことが、体感パフォーマンスとスムーズなユーザー体験に直結します。
そのためにも useCallback
の正しい理解と応用は欠かせません。
何でも useCallback
にすればいいわけではなく、適切な場所で使う判断力が必要です。
この記事の内容を踏まえて、実際のアプリケーションの中で「ここに使うべきか?」を意識しながら実装していくことが、Reactスキルの向上につながります。