useRef は、.current プロパティを持つミュータブルなrefオブジェクトを作成するReactフックで、コンポーネントのレンダー間で保持されます。useStateとは異なり、.currentを変更しても再レンダリングが発生しないため、useRefはDOM参照、タイマー、UIの再描画なしでレンダー間で保持する必要がある値の保存に最適です。Reactドキュメント(2025年)によると、refオブジェクトはコンポーネントのライフタイム中に一度作成され、レンダー間で変更されず、参照の安定性を保証します。
重要なポイント
useRef はReact 16.8で追加されたフックで、単一の.currentプロパティを持つミュータブルなrefオブジェクトを返します。初期値はコンポーネントのマウント時に.currentに割り当てられます。通常の変数との主な違いは、refオブジェクトが再レンダリングを生き残ることです。後続のレンダーでは、Reactはrefを再作成せず、同じオブジェクトを返します。
内部的には、useRef はuseStateやuseEffectと同じメカニズム(ファイバーノード上のフックのリンクリスト)を使用します。ただし、useStateとは異なり、Reactはrefオブジェクトの変更を追跡せず、更新をキューに入れず、再レンダリングをスケジュールしません。これにより、useRefは頻繁な変更下でもパフォーマンスに影響を与えない、非常に軽量なフックになります。
Reactチーム — Hooks FAQ(2025年) によると、useRefは基本的に値を保存する「箱」です。シンタックスシュガー:const ref = useRef(initialValue) は const ref = { current: initialValue } と同等ですが、Reactがすべてのレンダーで同じrefオブジェクトを保証する点が異なります。魔法ではなく、Reactによって管理される安定した参照にすぎません。
import { useRef } from 'react';
function Component() {
const countRef = useRef(0);
// countRef.current = 0 初期状態
// countRef.current = 5 変更後
// 再レンダリングは発生しません!
}
useRef の最も一般的なユースケースは、DOM要素への直接アクセスを取得することです。Reactはコンポーネントのマウント後に、elementRef.currentにDOMノードへの参照を割り当てます。これは、inputへのフォーカス、要素の寸法測定、アニメーションライブラリやReactレンダリングを使用しないライブラリとの統合に必要です。
Reactは自動的にDOM上のref参照を管理します。マウント時に実際のDOMノードを割り当て、アンマウント時にnullにします。これにより、.currentが常に最新の値を保持し、削除された要素へのぶら下がり参照によるメモリリークを防ぎます。手動でのクリーンアップは不要です。
function AutoFocusInput() {
const inputRef = useRef(null);
useEffect(() => {
// コンポーネントマウント後に入力をフォーカス
inputRef.current?.focus();
}, []);
return <input ref={inputRef} type="text" />;
}
React — Refs and DOMドキュメント(2025年) によると、宣言的操作(モーダルの開閉、可視性の管理)にはrefを使用しないでください。これらの目的にはstateとpropsを使用します。refは、宣言的に表現できない命令的操作(フォーカス、テキスト選択、サードパーティライブラリとの統合)を目的としています。
2番目に重要なユースケースは、レンダー間で保持する必要があるが、変更によって再レンダリングをトリガーすべきではない任意の値を保存することです。典型的な例:タイマー識別子(setInterval/setTimeout)、リクエストキャンセルフラグ、比較用の以前のprops値、UI出力に影響しないデータ。
これはuseEffectで特に便利です。useRefはタイマー識別子を保存し、クリーンアップでそれをクリアします。timerIdがuseStateに保存されている場合、setTimerIdを呼び出すたびに、ロジックにもUIにも不要な再レンダリングが発生します。useRefはオーバーヘッドなしで、不要な再レンダリングなしでこの問題を解決します。
function Timer() {
const intervalRef = useRef(null);
const start = () => {
intervalRef.current = setInterval(() => {
console.log('ティック');
}, 1000);
};
const stop = () => {
clearInterval(intervalRef.current);
};
useEffect(() => stop, []);
return <><button onClick={start}>Start</button><button onClick={stop}>Stop</button></>;
}
主なジレンマ — useRef と useStateの選択 — は1つの質問で解決します。「値が変更されたときに再レンダリングが必要か?」。はい — useState。いいえ — useRef。useStateはコンポーネントの出力に影響する状態を保存します。useRefは内部ロジックに必要なデータを保存しますが、UIには影響しません。
実際には、開発者はクロージャの問題を避けるためにコールバックを保存する目的でuseRefをよく使用します。例えば、useEffectがイベントを購読し、コールバックが現在のstateを必要とする場合、コールバックをuseRefに保存します。各レンダーでref.currentを新しい関数で更新すると、エフェクトは再購読せずに常に新しいコールバックを呼び出します。
| 基準 | useRef | useState |
|---|---|---|
| 再レンダリング | 変更時にトリガーしない | setStateごとにトリガー |
| 変更 | 直接:ref.current = 値 | セッター経由:setState(値) |
| JSXでの使用 | 使用しない(効果なし) | コンポーネント出力で使用 |
| 例 | タイマー、DOM参照、以前の値 | フォームデータ、UI状態、フラグ |
アンチパターンがあります。JSXで必要なデータにuseRefを使用し、その変更で再レンダリングを発生させたくない場合です。これにより非同期が発生します — ref.currentが既に新しいのにUIは古いデータを表示します。値がUIに表示される場合はuseStateを使用します。内部でのみ使用される場合はuseRefを使用します。
useRef + useEffectの組み合わせは、以前のprops値を追跡する標準的なパターンです。以前の値をrefに保存し、useEffectで現在の値と比較し、差分に基づいて決定を行います。これは、変更前の値を知る必要があるアニメーションで特に有用です。
function PriceDisplay({ price }) {
const prevPriceRef = useRef(price);
useEffect(() => {
const prevPrice = prevPriceRef.current;
if (price > prevPrice) {
animateUp();
} else if (price < prevPrice) {
animateDown();
}
prevPriceRef.current = price;
}, [price]);
return <span>${price}</span>;
}
Reactドキュメント — Hooks FAQ(2025年) によると、このパターンは「以前の値パターン」と呼ばれます。useRefがレンダー間で値を保持し、useEffectが変更の確定後に実行されるため機能します。最初にDOMが新しいpriceで更新され、次にuseEffectがそれを(まだrefにある)以前の値と比較し、ref.currentを現在の値に更新します。
最も頻繁な間違いは、JSXを計算するためにレンダーフェーズでref.currentを読み取ることです。ref.currentの変更は再レンダリングを引き起こさないため、コンポーネントは古い値を使用する可能性があります。.currentがUI出力に関与する場合はuseStateを使用します。refとstateを同期する必要がある場合は、useEffectを使用してrefからstateを更新します。
// ❌ 表示のためにレンダー中にref.currentを読み取らないでください
function BadComponent() {
const valRef = useRef(0);
return <p>{valRef.current}</p>; // 変更時に更新されません
}
// ✅ 表示にはstate、ロジックにはrefを使用
function GoodComponent() {
const [val, setVal] = useState(0);
const valRef = useRef(0);
return <p>{val}</p>;
}
もう1つのよくある間違いは、データが実際にUIに影響を与える場合に、コンポーネントの状態を保存する唯一の方法としてuseRefを使用することです。開発者は「不要な」再レンダリングを避けるためにuseRefを選択しますが、結果として更新されないUIを得ることになります。正しいアプローチ:UIデータにはuseStateを使用し、レンダリングに関与しない補助的な値にのみuseRefを使用します。
よくある質問
はい、これは一般的なパターン — 以前の状態パターンです。refを作成し、追跡する値が変更されるたびにuseEffectで更新します。レンダーとエフェクトの間、ref.currentには以前の値が含まれており、現在の値と比較できます。これには追加の再レンダリングは不要で、あらゆるデータ型で機能します。
Reactは、要素がレンダリングされて実際のDOMに追加された後にのみ、ref.currentにDOMノードの値を割り当てます。最初のレンダー中はコンポーネントがまだマウントされていないため、ref.currentは初期値(null)と等しくなります。ref.currentを介したDOMへのアクセスは、useEffect内またはマウント後に呼び出されるイベントハンドラ内でのみ可能です。
createRef はレンダリングのたびに新しいrefオブジェクトを作成します — これはクラスコンポーネントでのみ使用すべきです。useRef はrefを一度作成し、後続のすべてのレンダーで同じオブジェクトを返します。関数コンポーネントではuseRefのみを使用してください。createRefは再レンダリング時に値が失われる原因となります。オブジェクトが再作成されるためです。
はい、refオブジェクトは通常のpropとして渡せます:<Child inputRef={inputRef} />。子コンポーネントはinputRef.currentを介して使用します。ただし、refを子コンポーネントのDOM要素に直接渡すには、forwardRef — props経由でrefを転送できるHOCを使用します。forwardRefがない場合、refは子コンポーネント内のDOM要素に自動的にバインドされません。
要素にrefをバインドし、useEffect内でref.current.getBoundingClientRect() または ref.current.offsetWidth / offsetHeight を読み取ります。サイズ変更のリアクティブな追跡には、useEffect内でResizeObserverを使用します:observerを作成し、変更を購読し、新しい寸法でstateを更新します。クリーンアップでobserverの接続を解除することを忘れないでください。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。