KAKUTA TECH BLOG

Blog Article

React の useRef とは何か? state と違う「再レンダーしない値」の正体

はじめに

React を学んでいると、useStateuseEffect と並んで useRef という Hook を目にします。しかし、useRef は初見では役割が非常にわかりにくく、「いつ使うのか」「何のためにあるのか」が掴みにくい Hook です。

useState は「画面を更新するための値」、useEffect は「副作用を扱うための仕組み」と比較的わかりやすいですが、useRef は「値を保持する」と説明されても、state と何が違うのかがはっきりしません。

この記事では、useRef の本質を初学者にも理解できるように、なぜ必要なのか、いつ使うのか、これがないとどう困るのか、そして実際のコードを通して丁寧に解説します。

参考にしたリンク

https://react.dev/reference/react/useRef

https://react.dev/learn/referencing-values-with-refs

“Refs are like state variables that don’t trigger re-renders when you set them.”

出典:https://react.dev/learn/referencing-values-with-refs

この一文が、useRef の本質を非常によく表しています。

useRef とは何か?

useRef を一言で言うなら、再レンダーを発生させずに値を保持できる入れ物です。

React のコンポーネントは、state が変わると再レンダーされます。しかし、すべての値の変更が画面更新につながるわけではありません。画面とは無関係な値まで state で管理すると、無駄な再レンダーが発生してしまいます。

そこで登場するのが useRef です。useRef は値を保持できますが、その値を変更しても再レンダーは起きません。

この特徴こそが useRef が存在する理由です。

なぜ state ではなく useRef が必要なのか

React の設計思想は、「 UI は state の結果である 」という考え方です。そのため、画面に影響する値は state で管理する必要があります。

しかし、アプリには次のような値も存在します。

  • 前回の値を覚えておきたい
  • タイマー ID を保存したい
  • DOM 要素に直接アクセスしたい
  • スクロール位置を保持したい

これらは画面表示とは直接関係ありません。このような値を state に入れてしまうと、値が変わるたびに再レンダーが発生し、パフォーマンスが悪化します。

useRef は、この「画面と関係ないが保持したい値」を扱うために存在します。

もし useRef がなかった場合、これらをグローバル変数やクロージャで管理することになり、React の管理下から外れてしまいます。結果として、予期せぬバグの原因になります。

useRef の基本的な使い方

useRef は次のように使います。

const ref = useRef(初期値)

この ref は、次のような構造を持っています。

{
  current: 初期値
}

値は ref.current に保存されます。

ref.current = 新しい値

この代入をしても、コンポーネントは再レンダーされません。

ここが state との決定的な違いです。

useRef と state の違いを理解する

state は「画面を更新するための値」、useRef は「画面とは関係なく保持する値」です。

例えばカウンターを作る場合、state を使います。

const [count, setCount] = useState(0)

count が変わると画面が更新される必要があるからです。

一方で、クリック回数を裏で記録するだけなら、useRef で十分です。

const clickCount = useRef(0)

function handleClick() {
  clickCount.current++
}

この値は増え続けますが、画面は更新されません。

DOM に直接アクセスするための useRef

useRef の代表的な使い方が、DOM 要素への直接アクセスです。

React は基本的に DOM を直接触ることを推奨していませんが、フォーカスを当てる、スクロールするなどの操作は必要になります。

const inputRef = useRef(null)

useEffect(() => {
  inputRef.current.focus()
}, [])

return <input ref={inputRef} />

このように、ref を JSX の ref 属性に渡すことで、実際の DOM 要素にアクセスできます。

これは useRef の非常に重要な役割です。

前回の値を保持する useRef

useRef は、前回の値を覚えておく用途にもよく使われます。

const prevCount = useRef(0)

useEffect(() => {
  prevCount.current = count
}, [count])

これにより、「前回の count 」を保持できます。state ではこれを実現するのは難しくなります。

タイマーや ID を保持する useRef

setIntervalsetTimeout の ID を保持する場合も useRef が使われます。

const timerRef = useRef(null)

function startTimer() {
  timerRef.current = setInterval(() => {
    console.log("tick")
  }, 1000)
}

function stopTimer() {
  clearInterval(timerRef.current)
}

この ID は画面表示とは無関係なため、state で管理すべきではありません。

useRef はなぜ再レンダーしないのか

React は state の変更を検知して再レンダーしますが、useRef は単なるオブジェクトです。

React は ref.current の変更を監視していません。そのため、値が変わっても再レンダーは発生しません。

これは欠点ではなく、useRef の最大の特徴です。

いつ useRef を使うべきか

useRef の重要性を実感するのは、次のような場面です。

  • DOM を操作するとき
  • 前回の値を保持したいとき
  • 再レンダーさせたくない値を持ちたいとき
  • タイマーや外部 API の ID を管理するとき

これらはすべて、state では不適切なケースです。

useRef がないとどう困るか

useRef がなければ、開発者はこれらの値をコンポーネント外で管理することになります。すると、React のライフサイクルとずれてしまい、予期せぬタイミングで値が失われたり、バグが発生したりします。

useRef は、React の管理下で安全に「画面に関係ない値」を扱うための仕組みです。

具体例で整理する

useRef を使うケース

  • input にフォーカスを当てる
  • スクロール位置を保持する
  • 前回の値を記録する
  • タイマー ID を保持する

state を使うケース

  • 画面に表示する値
  • UI の変化に影響する値

state と useRef の違い

項目

state

useRef

再レンダー

発生する

発生しない

主な用途

UI の更新

値の保持

DOM 参照

不可

可能

前回値の保持

やや難しい

容易

まとめ

useRef とは、再レンダーを発生させずに値を保持できる仕組みです。

React において、すべての値を state で管理するべきではありません。画面と関係ない値は useRef に任せることで、パフォーマンスと設計の両方が改善されます。

ポイント整理
  • useRef は再レンダーしない値の入れ物
  • DOM へのアクセスに使う
  • 前回の値やタイマー ID を保持する
  • state と役割が明確に異なる

useRef の役割を理解すると、React の設計思想がよりはっきりと見えてきます。