useRef 完全入門【React】DOMアクセスと再レンダリングしない値の保持

ReactのuseRefとは何か、DOM要素への直接アクセスと再レンダリングをトリガしない値の保持という2つの用途を初心者向けに解説。useStateとの違いや具体的な使用例も紹介します。

#react#hooks#useref#javascript#frontend

「フォームを表示したときに自動でフォーカスを当てたい」「タイマーを止めるためにIDを保存しておきたい」——こういった処理に使うのが useRef です。

useRefuseState とは異なり、値を変えても再レンダリングが起きないという特性を持ちます。この特性を理解することで、適切な使い分けができるようになります。

この記事でわかること:

  • useRef の2つの用途(DOM 操作・値の保持)
  • useState との違い
  • フォームへのフォーカス・タイマー制御の実装例
  • レンダリング中に ref を読み書きしてはいけない理由

useRef とは?

useRef は、レンダリングに使わない値を保持するフックです。

const ref = useRef(initialValue);
パラメータ説明
initialValueref.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 の使い分け

特性useStateuseRef
値を変えたとき再レンダリングされる再レンダリングされない
使う目的画面に表示する値画面に表示しない値・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 公式ドキュメントを見る

関連記事