useRef 完全入門【React】DOMアクセスと再レンダリングしない値の保持
ReactのuseRefとは何か、DOM要素への直接アクセスと再レンダリングをトリガしない値の保持という2つの用途を初心者向けに解説。useStateとの違いや具体的な使用例も紹介します。
「フォームを表示したときに自動でフォーカスを当てたい」「タイマーを止めるためにIDを保存しておきたい」——こういった処理に使うのが useRef です。
useRef は useState とは異なり、値を変えても再レンダリングが起きないという特性を持ちます。この特性を理解することで、適切な使い分けができるようになります。
この記事でわかること:
useRefの2つの用途(DOM 操作・値の保持)useStateとの違い- フォームへのフォーカス・タイマー制御の実装例
- レンダリング中に ref を読み書きしてはいけない理由
useRef とは?
useRef は、レンダリングに使わない値を保持するフックです。
const ref = useRef(initialValue);| パラメータ | 説明 |
|---|---|
initialValue | ref.current の初期値(任意の型) |
戻り値は { current: initialValue } というオブジェクトです。ref.current に値を読み書きします。
useRef の2つの主な用途
用途1:DOM要素を直接操作する
import { useRef } from "react";
function SearchForm() {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current.focus(); // DOM のメソッドを直接呼び出す
};
return (
<>
{/* ref 属性に渡すと、inputRef.current にその DOM ノードが入る */}
<input ref={inputRef} type="text" placeholder="検索..." />
<button onClick={handleClick}>フォーカスを当てる</button>
</>
);
}コンポーネントがマウントされると、inputRef.current に <input> の DOM ノードが代入されます。
用途2:再レンダリングをトリガしない値を保持する
import { useRef, useState } from "react";
function Stopwatch() {
const [time, setTime] = useState(0);
const timerIdRef = useRef(null); // タイマーIDをrefで保持
const start = () => {
timerIdRef.current = setInterval(() => {
setTime((prev) => prev + 1);
}, 1000);
};
const stop = () => {
clearInterval(timerIdRef.current); // refからIDを取り出してタイマーを解除
timerIdRef.current = null;
};
return (
<>
<p>経過時間: {time}秒</p>
<button onClick={start}>スタート</button>
<button onClick={stop}>ストップ</button>
</>
);
}timerIdRef の値が変わっても画面は再レンダリングされません。タイマーIDは画面に表示する必要がないので、useRef に保存するのが適切です。
useState vs useRef の使い分け
| 特性 | useState | useRef |
|---|---|---|
| 値を変えたとき | 再レンダリングされる | 再レンダリングされない |
| 使う目的 | 画面に表示する値 | 画面に表示しない値・DOM操作 |
| 更新方法 | setState() | ref.current = 新しい値 |
// useState:画面に表示する値
const [count, setCount] = useState(0); // 変わると画面が更新される
// useRef:画面に表示しない値
const intervalId = useRef(null); // 変わっても画面は更新されない実用例:前回の値を記憶する
import { useRef, useState, useEffect } from "react";
function CounterWithHistory() {
const [count, setCount] = useState(0);
const prevCountRef = useRef(0); // 前回の値を記憶
useEffect(() => {
prevCountRef.current = count; // レンダリング後に更新
});
const prevCount = prevCountRef.current;
return (
<>
<p>現在: {count}</p>
<p>前回: {prevCount}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</>
);
}実用例:DOM のサイズを取得する
import { useRef, useState, useEffect } from "react";
function MeasureDiv() {
const divRef = useRef(null);
const [height, setHeight] = useState(0);
useEffect(() => {
// DOM がマウントされた後に高さを取得
setHeight(divRef.current.offsetHeight);
}, []);
return (
<>
<div ref={divRef} style={{ padding: "20px", background: "#f0f0f0" }}>
このdivの高さを計測します。
</div>
<p>高さ: {height}px</p>
</>
);
}注意:レンダリング中に ref を読み書きしてはいけない
// ❌ NG:レンダリング中に ref.current を書き換える
function BadComponent() {
const ref = useRef(0);
ref.current = ref.current + 1; // ← レンダリング中の書き換えは禁止
return <p>{ref.current}</p>;
}
// ✅ OK:イベントハンドラやuseEffectの中で読み書きする
function GoodComponent() {
const ref = useRef(0);
const handleClick = () => {
ref.current = ref.current + 1; // ← イベントハンドラ内はOK
};
return <button onClick={handleClick}>クリック</button>;
}レンダリング中に ref を書き換えると、コンポーネントの純粋性が壊れてバグの原因になります。
まとめ
useRefはレンダリングに影響しない値を保持するフック- 主な用途はDOM 要素への参照と再レンダリングをトリガしない値の保持
ref.currentに直接値を読み書きする(setStateのような更新関数はない)useStateとの違い:ref の変更では再レンダリングが起きない- イベントハンドラや
useEffectの中で読み書きし、レンダリング中は使わない
PR
useRef 公式ドキュメントで詳細を確認しよう
useRefのインタラクティブなサンプルや、useImperativeHandleとの組み合わせ方法は公式ドキュメントで確認できます。
useRef 公式ドキュメントを見る →