はじめに
ReactのHooksである useEffect、useRef、useCallback はそれぞれ便利ですが、組み合わせることでより実用的なユースケースが生まれます。
本記事では、「ボタンを押すと一定間隔でカウントが進み、停止もできる」簡単な例を使って、3つのHookを組み合わせて解説します。
各Hookの役割
- useRef:DOM要素やミューテーション可能な値の保持に使います。再レンダリングを発生させません。
- useEffect:副作用の処理に使います。マウント、アンマウント、依存値の変化に応じて発火します。
- useCallback:関数をメモ化して、不要な再生成を防ぎます。子コンポーネントへの渡しや、useEffectの依存に便利。
スタート・ストップ機能付きカウンター
Timer.tsx
import { useState, useRef, useEffect, useCallback } from "react";
export default function Timer() {
const [count, setCount] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
// カウントアップ関数(useCallbackでメモ化)
const tick = useCallback(() => {
setCount(prev => prev + 1);
}, []);
// スタート関数
const start = useCallback(() => {
if (!intervalRef.current) {
intervalRef.current = setInterval(tick, 1000);
setIsRunning(true);
}
}, [tick]);
// ストップ関数
const stop = useCallback(() => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
setIsRunning(false);
}
}, []);
// アンマウント時にクリーンアップ
useEffect(() => {
return () => {
stop();
};
}, [stop]);
return (
<div className="p-4 text-center">
<h2 className="text-xl mb-2">⏱️ Count: {count}</h2>
<button onClick={start} disabled={isRunning} className="px-4 py-2 bg-blue-500 text-white rounded mr-2">
Start
</button>
<button onClick={stop} disabled={!isRunning} className="px-4 py-2 bg-gray-500 text-white rounded">
Stop
</button>
</div>
);
}
🔍 ポイント解説
✅ useRefの活用
-
intervalRefはsetIntervalのIDを保持し、再レンダリングを防ぎつつ、start/stop間で情報を共有しています。
✅ useCallbackで関数をメモ化
-
tick,start,stopすべてuseCallbackでメモ化。useEffectの依存や、再レンダリングで不要に関数が再生成されるのを防ぎます。
✅ useEffectでクリーンアップ処理
- コンポーネントのアンマウント時に
stop()を呼び出してメモリリークを防いでいます。
おわりに
useRef, useEffect, useCallback をうまく組み合わせることで、より堅牢でパフォーマンスの良いReactコンポーネントを作ることができます。