ReactのuseRefは「DOM参照フック」だけではない
これまでの記事では、useRefのDOM操作について触れてきましたが、
実務レベルでReactを書いていると、UIには出さないけど、最新の状態を維持したい値が多く存在します。
1.スクロール座標
2.アニメーションの進捗
3.タイマーID
4.前回値
5.Web APIインスタンス
6.フラグ管理
7.イベントの最新ハンドラ
こうした値を、UIを再描画せずに持ち続けられるのがuseRefの強みです。
useRefの本質
再レンダリングさせずに“現在値”を持つ箱
useRef.current に代入してもUIは変わりません。
だからこそ、裏で動くロジック用の変数として最適です。
useRefの非DOM用途パターン
1. 高頻度で変化する値の保持(ドラッグ/カーソル/アニメ)
const posRef = useRef({ x: 0, y: 0 })
function onMove(e) {
posRef.current = { x: e.clientX, y: e.clientY }
}
クリックした座標の位置の値なども取得可能。
マウス追従UI、ドラッグ、チャートを使用したキャンバス描画などに。
2. タイマー制御(setInterval / setTimeout)
const timerRef = useRef<number | null>(null)
function start() {
timerRef.current = window.setInterval(() => {
console.log("tick")
}, 1000)
}
function stop() {
if (timerRef.current) clearInterval(timerRef.current)
}
ゲームやチャートの更新、定期データ取得などでよく使います。
タイマーのIDは画面とは関係ないので、refにメモしておくとスムーズに管理できます。
3. 前回値を記録して差分ロジック
const prevValueRef = useRef<number | null>(null)
function handle(value: number) {
console.log("前回:", prevValueRef.current, "現在:", value)
prevValueRef.current = value
}
スクロールが上方向か下方向か、数値が増えたか減ったかなど、
“前の値と比較して判断する”ロジックに便利です。
refに前回値を保存しておくことで、次の処理時に簡単に差分を取れます。
4. 一度だけ実行したい処理のフラグ管理
const initializedRef = useRef(false)
function initOnce() {
if (initializedRef.current) return
initializedRef.current = true
console.log("初回だけ実行")
}
初期化処理、APIコール制御、
無限ループ防止フラグとして使われます。
5. Web APIやライブラリインスタンスの保持
const audioRef = useRef<HTMLAudioElement | null>(null)
const playerRef = useRef<SomePlayer | null>(null)
例:
Audio / Video API
Three.js / D3
Chart.js
WebRTC / WebSocket
レンダリングがあっても、動画の再生や画面の描画を止めないなど
外部オブジェクトをReact再レンダリングの外で持ちたいときに。
6. 重いデータのキャッシュ
const cacheRef = useRef<Map<string, any>>(new Map())
function save(key: string, data: any) {
cacheRef.current.set(key, data)
}
APIレスポンス、計算結果など
再取得したくない一時データを保持。
“見せる”データではなく、“使う”データ向けです。
まとめ
useRefは「DOM操作のためのフック」という説明だけでは足りません。
実務では、UIではなくロジック側で動く値を扱う場面が多く存在します。
画面のレンダリングに関係する状態はstate
画面のレンダリング外で動く値はref
この意識があると、Reactコードの構造がクリアになり、
パフォーマンスの無駄も減ります。