useRefとは?
ReactのuseRef
フックは、レンダー間で変化しても再レンダーを引き起こさない「箱」を提供します。返されるオブジェクトは{ current: 値 }
という形で、コンポーネントのライフサイクル全体を通じて同じ参照を保持します。主な用途は次の二つです。
- DOM要素への参照:フォーム入力フォーカスやスクロール位置制御
- ミューテーション値の保持:レンダー外で更新されるカウンタや外部ライブラリのインスタンス
import { useRef, useEffect } from 'react';
function TextInputFocus() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} placeholder="自動でフォーカス" />;
}
仕組みと特徴
useRef(initialValue)
はレンダーのたびに 同じオブジェクト を返すref.current
の変更は React の再レンダーをトリガーしない- オブジェクト参照自体は固定だが
current
プロパティは自由に書き換え可能
この振る舞いにより、状態のように保持しつつUIに影響を与えないデータ を格納できます。
DOMリファレンスの実践例
フォームのフォーカス管理
複数ステップのフォームで次の入力要素へフォーカスを移すとき、useRef
が活躍します。
function MultiStepForm() {
const step1Ref = useRef(null);
const step2Ref = useRef(null);
const [step, setStep] = useState(1);
useEffect(() => {
if (step === 1) step1Ref.current.focus();
if (step === 2) step2Ref.current.focus();
}, [step]);
return (
<>
{step === 1 && <input ref={step1Ref} />}
{step === 2 && <input ref={step2Ref} />}
<button onClick={() => setStep(s => s + 1)}>次へ</button>
</>
);
}
スクロール制御
チャットアプリで新しいメッセージ追加時に最下部までスクロールする例。
function ChatWindow({ messages }) {
const bottomRef = useRef(null);
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
return (
<div className="chat-area">
{messages.map(m => <p key={m.id}>{m.text}</p>)}
<div ref={bottomRef} />
</div>
);
}
ミューテーション値の保持
レンダー回数を数える
状態にすると無限ループするような値は useRef
で保持。
function RenderCount() {
const countRef = useRef(0);
useEffect(() => { countRef.current += 1 });
return <p>レンダー回数: {countRef.current}</p>;
}
setIntervalのIDを保持
function Timer() {
const intervalRef = useRef(null);
const [count, setCount] = useState(0);
const start = () => {
if (intervalRef.current === null) {
intervalRef.current = setInterval(() => setCount(c => c + 1), 1000);
}
};
const stop = () => {
clearInterval(intervalRef.current);
intervalRef.current = null;
};
useEffect(() => stop, []); // アンマウント時に停止
return <>
<p>{count} 秒経過</p>
<button onClick={start}>開始</button>
<button onClick={stop}>停止</button>
</>;
}
forwardRefと組み合わせる
親コンポーネントから子のDOMへ直接アクセスする場合は forwardRef
が必要です。
const FancyInput = React.forwardRef((props, ref) => {
return <input className="fancy" ref={ref} {...props} />;
});
function Parent() {
const inputRef = useRef(null);
return <>
<FancyInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>フォーカス</button>
</>;
}
useImperativeHandleで公開APIを制御
子コンポーネント側で ref
経由の公開メソッドをカスタマイズできます。
const Canvas = React.forwardRef((props, ref) => {
const canvasRef = useRef();
useImperativeHandle(ref, () => ({
clear: () => canvasRef.current.getContext('2d').clearRect(0,0,300,150)
}));
return <canvas ref={canvasRef} width={300} height={150} />;
});
function DrawingApp() {
const canvasRef = useRef();
return <>
<Canvas ref={canvasRef} />
<button onClick={() => canvasRef.current.clear()}>クリア</button>
</>;
}
コールバックrefとの比較
種類 | 設定方法 | 特徴 |
---|---|---|
string ref | ref="myRef" | レガシー、廃止予定 |
callback ref | ref={node => { ... }} | 動的に複数refや条件分岐を管理 |
useRef + ref属性 | const r=useRef(); … ref={r} | 一般的。オブジェクトに一度だけセット |
パフォーマンスに与える影響
useRef
はレンダーをトリガーしないため、頻繁に変化する値を保持してもコストは低い。ただし current
の変更にUIを反映したい場合は、別途 useState
と組み合わせる必要がある。
よくある落とし穴
- refがnullのままアクセス
- 初回レンダー直後はまだDOMがセットされておらずnull。
useEffect
の中で読む。
- 初回レンダー直後はまだDOMがセットされておらずnull。
- currentを書き換えても画面が更新されない
useRef
は状態管理ではない。UIに反映が必要ならuseState
を使う。
- 依存配列にref.currentを入れてしまう
ref.current
の値が変わっても依存配列は検知しない。監視には別状態が必要。
総括
useRef
は DOMアクセス と ミューテーション値の永続化 を実現する万能ポケットです。再レンダーコストを抑えつつ、状態とは異なる形で情報を保持できるため、フォーム・アニメーション・外部ライブラリ統合など多様な場面で活用できます。ポイントは「UI描画に関与しない値」を見極め、状態との役割分担を明確にすることです。
コメント