はじめに
useRefってなんとなくはわかるけど、イマイチつかめないという初学者の人は多いはず。
今回はuseRefが力を発揮する状況にフォーカスして解説していきます
1. DOM要素への直接アクセス
useRef
の最も一般的な使用例の1つは、DOM要素に直接アクセスすることです。
import React, { useRef, useEffect } from 'react';
const FocusInput = () => {
const inputRef = useRef(null);
useEffect(() => {
// コンポーネントがマウントされた後に入力フィールドにフォーカスを当てる
inputRef.current?.focus();
}, []);
return (
<div>
<input ref={inputRef} type="text" placeholder="This input will be focused" />
</div>
);
};
この例では、useRef
を使用して入力フィールドへの参照を取得し、コンポーネントのマウント後に自動的にフォーカスを当てています。これは、useState
では直接実現できない機能です。
2. 以前の値の保持
useRef
はレンダリングをトリガーにせず、値を保持するのに適しています。
import React, { useState, useRef, useEffect } from 'react';
const CounterWithPrevious = () => {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
}, [count]);
const prevCount = prevCountRef.current;
return (
<div>
<p>Now: {count}, before: {prevCount}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
この例では、useRef
を使って前回のカウント値を保持しています。useState
を使うと再レンダリングが発生してしまうため、useRef
を使用するのが適していると言えます。
3. タイマーやインターバルのIDの保持
useRef
は、タイマーやインターバルのIDを保持するのに適しています。
import React, { useState, useRef, useEffect } from 'react';
const Timer = () => {
const [seconds, setSeconds] = useState(0);
const timerRef = useRef(null);
const startTimer = () => {
if (timerRef.current !== null) return; // タイマーが既に動いている場合は何もしない
timerRef.current = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
};
const stopTimer = () => {
if (timerRef.current === null) return; // タイマーが動いていない場合は何もしない
clearInterval(timerRef.current);
timerRef.current = null;
};
useEffect(() => {
// コンポーネントのアンマウント時にタイマーをクリアする
return () => {
if (timerRef.current !== null) {
clearInterval(timerRef.current);
}
};
}, []);
return (
<div>
<p>Seconds: {seconds}</p>
<button onClick={startTimer}>Start</button>
<button onClick={stopTimer}>Stop</button>
</div>
);
};
この例では、useRef
を使ってタイマーのIDを保持しています。これにより、コンポーネントの再レンダリング間でタイマーのIDを保持し、適切にタイマーを開始・停止できます。
4. 不必要な再レンダリングの回避
値の変更時に再レンダリングをトリガーしたくない場合、useRef
が有用です。
import React, { useRef } from 'react';
const HeavyComponent = () => {
const renderCount = useRef(0);
renderCount.current += 1;
return <div>This component has rendered {renderCount.current} times.</div>;
};
この例では、レンダリング回数をカウントしていますが、カウントの更新自体が再レンダリングをトリガーすることはありません。
5. コールバック関数の最新の値へのアクセス
useRef
は、useCallback
やuseMemo
でラップされたコールバック内で最新の値にアクセスする際に役立ちます。
import React, { useState, useRef, useCallback, useEffect } from 'react';
const CallbackExample = () => {
const [count, setCount] = useState(0);
const countRef = useRef(count);
useEffect(() => {
countRef.current = count;
}, [count]);
const handleClick = useCallback(() => {
console.log(`Current count: ${countRef.current}`);
}, []); // 依存配列が空なので、handleClickは一度だけ作成される
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={handleClick}>Log count</button>
</div>
);
};
この例では、useRef
を使用して最新のcount
値を保持し、メモ化されたhandleClick
関数内でその値にアクセスしています。
ここでおさらい
useCallbackとuseMemoの比較:類似点と相違点
ReactのuseCallbackとuseMemoは、両方ともメモ化(memoization)のためのフックですが、それぞれ異なる用途に最適化されています。両者の違いと使用すべき状況について簡単に復習しておきましょう。
1. 基本的な違い
useCallback
- 目的: 関数のメモ化
- 返り値: メモ化されたコールバック関数
- 使用例: イベントハンドラ、子コンポーネントに渡す関数
useMemo
- 目的: 値のメモ化
- 返り値: メモ化された値
- 使用例: 複雑な計算結果、オブジェクトや配列の生成
2. 構文の違い
useCallbackとuseMemoの構文は非常に似ていますが重要な違いがあります。以下で両者を並べて比較し、その違いを詳しく説明します。
基本構文
useCallback
const memoizedCallback = useCallback(callback, dependencies);
useMemo
const memoizedValue = useMemo(() => computation, dependencies);
詳細な比較
特徴 | useCallback | useMemo |
---|---|---|
第1引数 | コールバック関数 | 値を返す関数 |
第2引数 | 依存配列 | 依存配列 |
返り値 | メモ化されたコールバック関数 | メモ化された値 |
3. 具体例
useCallback
import React, { useCallback, useState } from 'react';
const ExampleComponent = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []); // 空の依存配列
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
};
この例では:
-
useCallback
は関数全体(() => { ... }
)を第1引数として受け取ります。 - 返り値(
handleClick
)は、メモ化された関数です。
useMemo
import React, { useMemo, useState } from 'react';
const ExampleComponent = () => {
const [number, setNumber] = useState(0);
const squaredNumber = useMemo(() => {
return number * number;
}, [number]);
return (
<div>
<p>Number: {number}</p>
<p>Squared: {squaredNumber}</p>
<button onClick={() => setNumber(prev => prev + 1)}>Increment</button>
</div>
);
};
この例では:
-
useMemo
は値を計算して返す関数(() => number * number
)を第1引数として受け取ります。 - 返り値(
squaredNumber
)は、メモ化された値(計算結果)です。
主な違い
-
関数 vs 値:
-
useCallback
は関数自体をメモ化します。 -
useMemo
は関数の結果(値)をメモ化します。
-
-
返り値の使用:
-
useCallback
の返り値は、そのまま関数として使用できます(例:イベントハンドラとして)。 -
useMemo
の返り値は、計算された値なので、直接使用します。
-
-
使用目的:
-
useCallback
は主に子コンポーネントに渡す関数の最適化に使用します。 -
useMemo
は複雑な計算や大きなオブジェクトの生成を最適化するのに使用します。
-
4. いつ使うべきか
useCallbackを使うべき時
- 子コンポーネントに関数を渡す場合、特に子コンポーネントが
React.memo
でラップされている場合 - useEffectの依存配列に関数を含める場合
- パフォーマンスプロファイリングで、関数の再作成が問題になっている場合
useMemoを使うべき時
- 計算コストが高い処理の結果をメモ化する場合
- オブジェクトや配列を新しく作成する処理をメモ化する場合
- レンダリング中に行われる処理を最適化したい場合
5. 注意点
- 過度の最適化は避けるべきです。これらのフックの使用自体にもコストがあるため、本当に必要な場合にのみ使用しましょう。
- 依存配列を正しく設定することが重要です。依存配列が適切でないと、期待通りの動作をしない可能性があります。
- これらのフックは、主にパフォーマンス最適化のためのものです。コードの可読性や保守性を損なわない範囲で使用しましょう。
useCallbackとuseMemoのまとめ
useCallbackとuseMemoは、どちらもReactアプリケーションのパフォーマンスを最適化するための強力なツールです。useCallbackは関数のメモ化に、useMemoは値のメモ化に使用します。適切に使用することで、不必要な再計算や再レンダリングを防ぎ、アプリケーションのパフォーマンスを向上させることができます。
記事全体まとめ
useRef
は以下の状況で使用することを検討しましょう
- DOM要素への直接アクセスが必要な場合
- レンダリングをトリガーせずに値を保持する必要がある場合
- タイマーやインターバルのIDを管理する場合
- 不必要な再レンダリングを避けたい場合
- メモ化されたコールバック内で最新の値にアクセスしたい場合