はじめに
ReactでDOM要素を直接操作したいとき、useRef
フックを使うことで対象ノードにアクセスすることができます。本記事では、以下のような代表的なユースケースを実例コードとともに紹介します。
1. スクロール操作
ボタンで特定のセクションに移動
ScrollExample.tsx
import React, { useRef } from "react"
export default function ScrollExample() {
const part1Ref = useRef<HTMLDivElement>(null)
const part2Ref = useRef<HTMLDivElement>(null)
const part3Ref = useRef<HTMLDivElement>(null)
const handleScroll = (ref: React.RefObject<HTMLDivElement>) => {
ref.current?.scrollIntoView({ behavior: "smooth" })
}
return (
<>
<div style={{ position: "fixed", background: "#fff", zIndex: 10 }}>
<button onClick={() => handleScroll(part1Ref)}>第一部</button>
<button onClick={() => handleScroll(part2Ref)}>第二部</button>
<button onClick={() => handleScroll(part3Ref)}>第三部</button>
</div>
<div ref={whiteRef} style={{ height: "100vh", background: "white" }} />
<div ref={yellowRef} style={{ height: "100vh", background: "yellow" }} />
<div ref={blackRef} style={{ height: "100vh", background: "black" }} />
</>
)
}
🔍 解説:
ref
を使うことで、各セクション(第一部/第二部/第三部)のDOMノードに直接アクセスできます。scrollIntoView()
メソッドは、その要素が画面に見えるようにスクロールしてくれる便利なAPIです。class名やidに依存しない ので、DOM構造が変わっても安心してスクロールできます。
2. 初期フォーカスの設定
ページを開いたら自動で入力欄にカーソルを合わせる
AutofocusInput.tsx
import React, { useEffect, useRef } from "react"
export default function AutofocusInput() {
const inputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
inputRef.current?.focus()
}, [])
return (
<div>
<label>Email:</label>
<input ref={inputRef} type="email" placeholder="your@email.com" />
</div>
)
}
🔍 解説:
- フォームで「ページを開いたら自動的に入力欄にフォーカスさせたい」ことはよくあります。
useRef
でinput要素を取得し、useEffect
で初回マウント時に.focus()
を呼べば、実現可能です。React的にフォーカスを操作したいときはこのパターンが定番です。
3. Videoプレイヤーの再生・停止制御
VideoPlayer.tsx
import React, { useRef, useState } from "react"
export default function VideoPlayer() {
const videoRef = useRef<HTMLVideoElement>(null)
const [isPlaying, setIsPlaying] = useState(false)
const togglePlay = () => {
if (!videoRef.current) return
isPlaying ? videoRef.current.pause() : videoRef.current.play()
setIsPlaying((prev) => !prev)
}
return (
<div>
<video ref={videoRef} width={320}>
<source src="https://www.w3schools.com/html/mov_bbb.mp4" type="video/mp4" />
</video>
<button onClick={togglePlay}>{isPlaying ? "Pause" : "Play"}</button>
</div>
)
}
🔍 解説:
ref
を使って<video>
要素の.play()
や.pause()
を直接呼び出すことで、状態に応じた制御が可能になります。HTML5のビデオ要素はコントロールが複雑になりやすいので、useRef
経由で直接操作できると便利です。
4. モーダル外クリックで閉じる
ClickOutsideModal.tsx
import React, { useEffect, useRef } from "react"
interface Props {
onClose: () => void
}
export default function ClickOutsideModal({ onClose }: Props) {
const modalRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const handleClick = (e: MouseEvent) => {
if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
onClose()
}
}
document.addEventListener("mousedown", handleClick)
return () => document.removeEventListener("mousedown", handleClick)
}, [onClose])
return (
<div ref={modalRef} style={{ background: "#fff", padding: "2rem" }}>
<h2>モーダル</h2>
<p>外側をクリックすると閉じます</p>
</div>
)
}
🔍 解説:
ref
を使うことで「自分自身がクリックされたかどうか」を判定できます。- Reactでは
document.addEventListener
を使って外側のクリックを検知し、ref.current.contains()
でモーダル外かを判断します。モーダルやドロップダウンの定番パターンです。
5. フィールドノート(メモチャット)
このコンポーネントは、簡易的なチャット風のメモアプリです。
FieldNotes.tsx
import * as React from "react"
export default function FieldNotes() {
const [notes, setNotes] = React.useState([
"ReactコンポーネントはUIの見た目とロジックをまとめて管理します。",
"関数を組み合わせるように、コンポーネントも組み合わせてUIを作成できます。",
"JSXはJavaScriptの力とHTMLの読みやすさを融合しています。",
"フックを使うことで、ロジックの再利用が簡単になります。",
])
const newNoteRef = React.useRef<HTMLLIElement>(null)
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
const form = e.target as HTMLFormElement
const formData = new FormData(form)
const newNote = formData.get("note")
if (typeof newNote === "string" && newNote.trim()) {
setNotes([...notes, newNote])
form.reset()
}
}
React.useEffect(() => {
if (newNoteRef.current) {
newNoteRef.current.scrollIntoView({ behavior: "smooth" })
}
})
return (
<div>
<article>
<h1>Field Notes</h1>
<div>
<ul>
{notes.map((msg, index) => (
<li
ref={index === notes.length - 1 ? newNoteRef : null}
key={index}
>
{msg}
</li>
))}
</ul>
<form onSubmit={handleSubmit}>
<input
required
type="text"
name="note"
placeholder="メモを入力してください..."
/>
<button type="submit">
送信
</button>
</form>
</div>
</article>
</div>
)
}
🔍 解説:
useState
でメモのリストを管理し、useRef
で最新メモのDOM要素を参照します。- メモを追加すると、
scrollIntoView({ behavior: \"smooth\" })
で自動的に最新メモへスクロールします。- フォーム送信時に新しいメモを追加し、空欄や空白のみの入力は無視します。
Certainly! Here’s an article section for your FollowTheLeader component, stripped of styling and following your requested format:
6. クリックした位置にマーブルが移動する
クリックした位置にマーブル(円)がスムーズに移動します。
FollowTheLeader.tsx
import React, { useEffect, useRef, useState } from "react"
export default function FollowTheLeader() {
const getCenter = (): [number, number] =>
[window.innerWidth / 2 - 32, window.innerHeight / 2 - 32]
const [position, setPosition] = useState<[number, number]>([0, 0])
const boxRef = useRef<HTMLDivElement>(null)
const [initialized, setInitialized] = useState(false)
useEffect(() => {
setPosition(getCenter())
setInitialized(true)
const handleResize = () => setPosition(getCenter())
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
if (!boxRef.current) return
const { width, height } = boxRef.current.getBoundingClientRect()
setPosition([
event.clientX - width / 2,
event.clientY - height / 2,
])
}
return (
<div onClick={handleClick}>
<div
ref={boxRef}
style={{
position: "absolute",
left: 0,
top: 0,
width: 64,
height: 64,
borderRadius: "50%",
transform: `translate(${position[0]}px, ${position[1]}px)`,
opacity: initialized ? 1 : 0,
}}
/>
</div>
)
}
🔍 解説:
useRef
でマーブルのDOM要素を参照し、クリック時の座標からマーブルの中心がクリック位置に来るように座標を計算しています。- ウィンドウリサイズ時も中央に再配置されるように
useEffect
でイベントリスナーを追加しています。
終わりに
useRef
を使えば、ReactでもDOM操作が必要な場面に柔軟に対応できます。特に次のような場面で効果を発揮します:
- 初期化時の操作(フォーカス、スクロール)
- メディア制御
- モーダルやドロップダウンなどのUI操作
- Intersection Observerを使った表示判定