React: useRef と DOM の操作
最終更新:2026-10-26
useRefこれはまるで金庫のようなものです。中に何でも収納でき、取り出してもその状態は変わりません。useStateとは異なり、refの値を変更しても、コンポーネントの再レンダリングはトリガーされません。
1. 学習内容
- useRef の基本的な使い方(DOM リファレンス)
- useRef は(再レンダリングをトリガーすることなく)変更可能な値を格納します
- forwardRef: 子コンポーネントへの参照を渡す
- useImperativeHandle は子コンポーネントのメソッドを公開します
- useRef と useState のどちらを選ぶか
2. 検索バーの物語
useRefの2つの主な用途
flowchart LR
subgraph "Uses1: DOMQuote"
A[useRef] --> B["ref={inputRef}"]
B --> C["inputRef.current.focus()"]
end
subgraph "Uses2: Variable-Value Storage"
D[useRef] --> E[".current = value]
E --> F[Does not trigger a repaint]
end
G[forwardRef] --> H[Passed from the parent componentref]
H --> I[Child Component ExposureDOM]
style A fill:#e3f2fd,stroke:#1565c0
style D fill:#e8f5e9,stroke:#2e7d32
style G fill:#fff3e0,stroke:#e65100
(1) 課題:入力フィールドにフォーカスを当てるには、DOMを操作する必要がある
ボブは検索ページを作成し、ページが読み込まれた際に検索ボックスに自動的にフォーカスが当たるようにしたかった:
▶ 試してみようボブはuseStateを使って、以下を実装したいと考えています:
(2) useRef を用いた解決策
▶ 試してみよう
出力:
TEXT
ボタン:Select All Text、Scroll to the bottom、Measure the height。入力:自動フォーカスの入力フィールド。useEffect が副作用を管理。useRef で DOM を参照
利点:useRef は可変オブジェクト { current: null } を返し、.current は実際の DOM 要素です。セレクタや getElementById は不要で、React と自然に連携します。
3. useRefの2つの主な用途
| 目的 | ref.current が指す先 | レンダリングの更新をトリガーする | 代表的なシナリオ |
|---|---|---|---|
| DOM リファレンス | DOM 要素 | ❌ | フォーカス、スクロール、寸法の測定 |
| 変数の格納 | 任意の JavaScript 値 | ❌ | タイマー ID、前回の値、レンダリングされていない状態 |
(1) ユースケース 1: DOM 参照
最も一般的な用途は、DOM要素の取得、フォーカスやサイズ、スクロール位置の操作などです。
| 操作 | コード | 説明 |
|---|---|---|
| フォーカス | inputRef.current.focus() |
オートフォーカス入力欄 |
| テキストを選択 | inputRef.current.select() |
入力フィールド内のすべてを選択 |
| スクロール先 | divRef.current.scrollIntoView() |
特定の要素までスクロール |
| 寸法を読み取る | divRef.current.offsetHeight |
要素の高さを取得 |
| 動画を再生 | videoRef.current.play() |
動画再生の操作 |
▶ サンプル:5つの一般的なDOM操作
▶ 試してみよう出力:
TEXT
自動フォーカスの入力フィールド、"Select All Text" ボタン、スクロール可能な 20 行リスト("Scroll to bottom" 付き)、"Measure the height" 付きのボックス → px 数値を表示
(2) ユースケース 2:変数の値を保存する(再描画をトリガーせずに)
これがuseRefの隠し技です。どんな値でも格納でき、その値が変わってもコンポーネントは再レンダリングされません。
▶ 試してみよう4. useRef 対 useState
| ディメンション | useRef | useState |
|---|---|---|
| 戻り値 | { current: initial } |
[value, setter] |
| 変更方法 | ref.current = newValue |
setState(newValue) |
| 変更後のレンダリング | ❌ トリガーされない | ✅ 再レンダリングがトリガーされる |
| 読み込まれるタイミング | 最新の値を即座に読み込む | 次のレンダリングが行われるまで読み込まない |
| ユースケース | DOM操作、タイマーID、以前の値 | UIに表示されるデータ |
出力:
TEXT
表示:"DOM Example of Operation"。ボタン:Select All Text、Scroll to the bottom、Measure the height。入力:自動フォーカスの入力フィールド。useEffect が副作用を管理。useRef で DOM/変数を参照
▶ サンプル:useRef を使用して前の値を保存する
▶ 試してみよう
出力:
TEXT
現在:0、前回:(first time)。+1 をクリック → 現在:1、前回:0。クリックのたびに現在値と前の値を表示する。
5. forwardRef:親コンポーネントによる子コンポーネントのDOMの制御
デフォルトでは、関数コンポーネントは自身のDOM参照を公開しません。forwardRef を使用することで、子コンポーネントは親コンポーネントの ref を受け取ることができます:
6. useImperativeHandle: 特定のメソッドを公開する
DOM要素全体を公開したくない場合は、useImperativeHandle を使用して特定のメソッドのみを公開してください:
7. 完全な例:リッチテキストエディタ
▶ 実行例を展開(107 行)
出力:
TEXT
状態:count(setter: setCount)。ボタン:setCount(c => c + 1)}>+1。useEffect が副作用を管理。useRef で DOM/変数を参照
期待される出力:太字、斜体、下線、リスト、H2見出し、その他の書式設定オプションをサポートするリッチテキストエディタ。コンテンツは、
refを通じて外部で保存、読み込み、またはクリアすることができます。
▶ サンプル:useRef を使用して、外部からクリックされた際にポップアップウィンドウを閉じる
▶ 試してみよう
出力:
TEXT
"Menu ▾" ボタン → ドロップダウンメニュー(Profile、Settings、Logout)。外側をクリック → useClickOutside フックで自動的に閉じる。
出力:
TEXT
スロットルボタン:連打は無視される(最大 1 秒に 1 回)。クリックカウンター + "Throttled!" のフィードバック。useRef で lastClick のタイムスタンプを保存
▶ サンプル:useRef を使用してタイマーを保存し、スロットリング機能付きボタンを実装する
▶ 試してみよう
出力:
TEXT
クリック → "Clicked!" + カウンター。1 秒以内に再クリック → "Too fast! Wait 1 second."(赤)。useRef のタイムスタンプでスロットルを実装。
出力:
TEXT
表示:"Click Me"。状態:clicks(setter: setClicks)、feedback(setter: setFeedback)。ボタン:Click Me。useRef で DOM/変数を参照。タイマーベースの挙動
▶ サンプル:forwardRef を使用して、フォーカス可能な Input コンポーネントをラップする
JSX
const FancyInput = React.forwardRef(function FancyInput({ label, error, ...props }, ref) {
const internalRef = useRef(null)
useImperativeHandle(ref, () => ({
focus: () => internalRef.current?.focus(),
selectAll: () => {
const el = internalRef.current
if (el) { el.focus(); el.select() }
},
scrollIntoView: () => internalRef.current?.scrollIntoView({ behavior: 'smooth' }),
}))
return (
<div style={{ marginBottom: 12 }}>
<label style={{ display: 'block', marginBottom: 4, fontSize: 14, fontWeight: 500 }}>{label}</label>
<input
ref={internalRef}
style={{ width: '100%', padding: 8, borderRadius: 4, border: `1px solid ${error ? '#ff4d4f' : '#d9d9d9'}`, outline: 'none' }}
{...props}
/>
{error && <p style={{ color: '#ff4d4f', fontSize: 12, margin: '4px 0 0' }}>{error}</p>}
</div>
)
})
function FormWithFancyInput() {
const emailRef = useRef(null)
function handleSubmit(e) {
e.preventDefault()
emailRef.current?.focus()
}
return (
<form onSubmit={handleSubmit} style={{ maxWidth: 400, margin: '0 auto' }}>
<h3>Sign Up</h3>
<FancyInput ref={emailRef} label="Email" type="email" placeholder="you@example.com" />
<FancyInput label="Password" type="password" placeholder="At least 6 characters" />
<button type="submit" style={{ padding: '8px 24px', cursor: 'pointer' }}>Submit</button>
</form>
)
}
出力:
TEXT
登録フォーム:ラベル付きの Email と Password 入力フィールド、エラー表示のスタイル付き。送信時に email にフォーカス。focus()、selectAll()、scrollIntoView() メソッドを公開。
❓ よくある質問
Q すべてのコンポーネントで forwardRef を使用する必要がありますか?
A いいえ。以下のケースでのみ使用してください:① 親コンポーネントが子コンポーネントの DOM を直接操作する必要がある場合(例:入力フィールドにフォーカスを当てたり、メディアの再生を制御したりする場合);② 再利用可能なフォームコンポーネントをカプセル化する場合(親コンポーネントがフォーカスや選択を制御できるようにするため)。単にデータを渡すだけなら、props を使用すれば十分です。
Q
useRef は useState の代わりになりますか?A いいえ。
useRef を変更してもコンポーネントの再レンダリングは発生しませんが、setState を変更した場合は再レンダリングが発生します。値を UI に表示する必要がある場合や、レンダリング結果に影響を与える必要がある場合は、useState を使用する必要があります。単に「ユーザーに表示する必要のないデータ」(インターバル ID、前回のレンダリング時の値、WebSocket インスタンスなど)を保存する場合は、useRef を使用してください。useRef は、「再レンダリングをトリガーしない変数コンテナ」と考えてください。📖 まとめ
useRefが{ current: initialValue }に戻る;.currentを変更しても 再レンダリングは発生しない- 2つの用途:① DOM参照(フォーカス、サイズ、スクロール) ② 変数値の保存(タイマーID、前回の値)
forwardRef: 親コンポーネントが ref を通じて子コンポーネントの DOM を制御できるようにするuseImperativeHandle: 親コンポーネントが必要とするAPIのみを提供することで、公開範囲を制限する方法- ref に対して操作を行う前には、必ず null かどうかを確認してください:
ref.current?.focus()
📝 練習問題
- 基本演習(難易度 ⭐):マウントされた際に自動的にフォーカスを取得する
AutoFocusInputコンポーネントを作成し、親コンポーネントから呼び出せる 2 つのメソッド、focus()とclear()を実装してください。 - 上級演習(難易度 ⭐⭐):
ClickCounterコンポーネントを作成し、useRefを使用してクリック数を追跡し(UI には表示しない)、useEffectを使用して、レンダリングのたびにコンソールに「X 回クリックされました」とログを出力してください。 - 課題(難易度:⭐⭐⭐):
InfiniteScrollコンポーネントを作成し、useRefを使用して、センチネル要素(ページ最下部の特定の要素)がビューポートに入ったことを検知し、それによってさらなるデータの読み込みをトリガーするようにします。IntersectionObserverAPI を使用してください。