ここまでは「Reactに任せる」話でした。この回は任せない部分です。
Reactは画面の描き直しを引き受けてくれますが、フォーカスを移す・大きさを測る・スクロールするといった操作までは面倒を見ません。そこだけ、DOMを直接触ります。
refはDOMへの取っ手
const inputRef = useRef(null)
<input ref={inputRef} />
<button onClick={() => inputRef.current?.focus()}>入力欄へ</button>refを付けた要素の実物のDOMがinputRef.currentに入ります。?.が要るのは、描かれる前はnullだからです。
refはDOM以外も入れられます。よく使うのはタイマーIDの保管です。
const timerRef = useRef(null)
function start() {
timerRef.current = window.setTimeout(() => { /* … */ }, 1000)
}
function stop() {
if (timerRef.current !== null) clearTimeout(timerRef.current)
}これをstateで持つと、IDを覚えただけで画面が描き直されます。無駄なうえに、次の描画までは古いIDのままです。
React 19:refはただのprops
自分で作った部品にrefを渡したい場面があります。
<SearchInput ref={inputRef} label="さがす" />React 18まではこれが素通りしなかったので、forwardRefで包む必要がありました。React 19からは、refを普通のpropsとして受け取れます。
function SearchInput({ ref, label }) {
const id = useId()
return (
<p>
<label htmlFor={id}>{label}</label>
<input id={id} ref={ref} type="search" />
</p>
)
}ポータル——JSXの位置と、DOMの位置を分ける
モーダルを作ると、必ずこれに当たります。
<div className="overflow-hidden rounded-2xl">
<BirdCard />
<Modal /> {/* ← 親のoverflow-hiddenで切られる */}
</div>親のoverflow: hidden・position・z-indexに閉じこめられます。CSSで戦っても勝てません。DOM上の位置が悪いからです。
createPortalは、JSXの位置はそのままに、実際のDOMだけを別の場所に置きます。
import { createPortal } from 'react-dom'
function Modal({ isOpen, onClose, title, children }) {
const dialogRef = useRef(null)
const titleId = useId()
useEffect(() => {
const dialog = dialogRef.current
if (!dialog) return
if (isOpen) dialog.showModal()
else dialog.close()
}, [isOpen])
return createPortal(
<dialog ref={dialogRef} aria-labelledby={titleId} onClose={onClose} className="rounded-2xl p-6">
<h2 id={titleId} className="font-bold">{title}</h2>
{children}
<button type="button" onClick={onClose}>閉じる</button>
</dialog>,
document.body, // ← 実際に置く場所
)
}実際に動かして確かめると、こうなっていました。
| 確かめたこと | 結果 |
|---|---|
<dialog>の親要素 | BODY(深い場所に書いたのに) |
開いたあとのopen属性 | 付いている |
| フォーカスの位置 | ダイアログの中(自動で移った) |
aria-labelledbyと見出しのid | どちらも_r_1_で一致 |
イベントは元のJSXの位置を伝わります。DOMはbody直下にあるのに、onCloseは書いた場所の親に届きます。「見た目のツリー」と「Reactのツリー」が別、というのがポータルの正体です。
公式ドキュメントも見てみる
ja.react.dev外部サイトref で DOM を操作する – ReactReact公式の「refでDOMを操作する」。使ってよい場面と、避けるべき場面の線引きが具体的に書かれていますこのレッスンのまとめ
refはDOMへの取っ手。refを付けた要素の実物がcurrentに入る- 画面に出るならstate、出ないならref。refを変えても再描画されない
- タイマーIDのような「覚えておくだけの値」もrefが向いている
- React 19では
refが普通のprops。forwardRefは不要(既存コードでは読めるように) refにコールバックを渡すと付け外しで呼ばれ、React 19から後片づけを返せるcreatePortalはJSXの位置を変えずにDOMだけ移す。親のoverflowやz-indexから逃げられる- ポータルでもイベントは元のJSXの位置を伝わる
- モーダルは
<dialog>+showModal()。フォーカスの閉じこめとEscapeが付いてくる
次は、まだ扱っていない残りのフックです。