1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ReactのuseRefの実務での使いどころ

Posted at

背景

これまでReactで使うフックは useStateuseEffect が多く、useRef に関しては「DOM にアクセスするためのフック」くらいの理解で止まってしまい、使いどころが分からずモヤモヤしていました。

  • 「値を保持できるのにレンダーが走らないってどういうこと?」
  • 「それなら全部 useRef でいいんじゃないの?」
  • 「DOM参照以外に何に使うの?」

といった疑問が常に頭の中にあり、正直 useRef は「知っているけど使いこなせないフック」でした。

しかし実際のプロジェクトでは、useRef を正しく理解して使うことで 「不要な再レンダーを避けられる」「前回の値を記録できる」「タイマーや外部リソースを安定して扱える」 といった便利さを実感しました。


useRefとは

useRefの基本

useRef の概要は、「再レンダーされても値を保持し続けるための箱」 です。
この箱は .current というプロパティを持ち、そこに任意の値を格納できます。

const ref = useRef(0);
console.log(ref.current); // => 0
ref.current = 100;
console.log(ref.current); // => 100

ここで重要なのは、ref.current の値を更新してもコンポーネントは再レンダーされないという点です。

useStateとの違い

よく比較されるのが useState で、両方とも「値を保持する」役割を持ちますが、挙動に違いがあります。

フック 値を更新したときの挙動 主な用途
useState 値を更新すると再レンダーが走る UI に反映したい値の管理
useRef 値を更新しても再レンダーは発生しない 再レンダー不要な値や DOM 参照の保持

直感的なイメージ

  • useState「値の変化を画面に反映させたいとき」(例: カウンターの数値)
  • useRef「値の変化を画面に表示する必要はないとき」(例: setInterval の ID、前回の入力値、スクロール位置など)

DOM参照にも使える

useRef のもうひとつの代表的な用途は DOM要素へのアクセス です。
React では直接 DOM を触ることを避ける設計が基本ですが、どうしても必要な場面(フォーカス操作など)があります。

const inputRef = useRef<HTMLInputElement>(null);

return (
  <>
    <input ref={inputRef} />
    <button onClick={() => inputRef.current?.focus()}>フォーカス</button>
  </>
);

ref 属性に useRef を渡すと、.current に実際の DOM 要素が格納され、直接操作できます。


3. 基本的な使い方

① DOM要素へのアクセス

もっとも分かりやすい使い方は DOM操作 です。
例えば、ボタンを押したら入力欄にフォーカスするケース。

import { useRef } from "react";

export default function App() {
  const inputRef = useRef<HTMLInputElement>(null);

  return (
    <>
      <input ref={inputRef} placeholder="名前を入力" />
      <button onClick={() => inputRef.current?.focus()}>
        フォーカスする
      </button>
    </>
  );
}

refinput に渡すと、inputRef.current に DOM 要素が入り、focus() を呼び出せます。

② 値を保持する(再レンダー不要)

useRefコンポーネントが再レンダーされても値を保持します。
例えば「前回の値を記録する」ケース。

import { useState, useEffect, useRef } from "react";

export default function App() {
  const [count, setCount] = useState(0);
  const prevCount = useRef<number>();

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

  return (
    <div>
      <p>現在: {count}</p>
      <p>前回: {prevCount.current}</p>
      <button onClick={() => setCount(c => c + 1)}>+1</button>
    </div>
  );
}

prevCount.current に前回の値が保存され、再レンダー後も消えません。
useState だと再レンダーを引き起こしてしまいますが、useRef なら裏でこっそり保持できます。

③ setInterval の ID 管理

useRefタイマーや外部リソースの ID を保持するのにも便利です。

import { useRef } from "react";

export default function Timer() {
  const timerId = useRef<number | null>(null);

  const start = () => {
    if (!timerId.current) {
      timerId.current = window.setInterval(() => {
        console.log("tick");
      }, 1000);
    }
  };

  const stop = () => {
    if (timerId.current) {
      clearInterval(timerId.current);
      timerId.current = null;
    }
  };

  return (
    <>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
    </>
  );
}

timerIduseState で管理すると不要な再レンダーが発生しますが、useRef なら再レンダーせずに保持できます。


実務でよくある使いどころ

useRef は公式ドキュメントでは「DOMアクセス」によく使われると説明されていますが、実際のプロジェクトではそれ以上に便利なケースが多いです。ここでは、自分が実務で遭遇したりよく見かけるパターンをまとめます。

① フォーム入力でのフォーカス制御

バリデーションエラーが出たときに、最初のエラー項目へ自動でフォーカスするようなケースです。

if (!formData.name) {
  inputRef.current?.focus();
}

ユーザー体験を向上させるために 「エラー箇所へスクロール or フォーカス」 する処理はよくあります。

② スクロール位置の保持

SPAでは、ページ遷移後に戻ったときに前回のスクロール位置を復元したい場面があります。

const scrollY = useRef(0);

useEffect(() => {
  const handleScroll = () => {
    scrollY.current = window.scrollY;
  };
  window.addEventListener("scroll", handleScroll);
  return () => window.removeEventListener("scroll", handleScroll);
}, []);

再レンダーに影響を与えないので、スクロール位置や座標系の保存にぴったりです。

③ 非同期処理のキャンセルフラグ

useEffect 内で非同期処理を行うとき、アンマウント後に setState を呼んでエラーになるのを避けるために useRef でフラグを持たせる方法があります。

const isMounted = useRef(true);

useEffect(() => {
  return () => {
    isMounted.current = false;
  };
}, []);

useEffect(() => {
  fetch("/api/data").then(res => {
    if (isMounted.current) {
      // 安全に更新できる
      setData(res);
    }
  });
}, []);

実務では「非同期処理とコンポーネントのライフサイクルを安全に扱う」ための小技としてよく使います。

④ 前回の値を比較したいとき

例えば 検索フォームで「前回の検索条件」と「今回の検索条件」を比較して差分だけ API に投げるような場面です。

const prevQuery = useRef<string>("");

useEffect(() => {
  if (prevQuery.current !== query) {
    fetchData(query);
    prevQuery.current = query;
  }
}, [query]);

「前回の値を保持して差分を見る」というパターンは地味によく出てきます。


注意点とまとめ

注意点・落とし穴

実務で useRef を使っていて、最初にハマりやすいポイントを挙げます。

  1. 値を更新しても画面に反映されない

    • useRef の更新は再レンダーを引き起こさないので、UI に表示したい値は必ず useState を使うべきです。
    • ref.current を変えたのに表示が更新されない」というのは典型的な勘違いポイント。
  2. 初期値は null になることが多い

    • DOM参照に使う場合、最初のレンダー時はまだ要素が存在しないので ref.currentnull
    • TypeScript を使う場合は useRef<HTMLInputElement>(null) のように型に null を許容する必要があります。
  3. やりすぎ注意

    • なんでもかんでも useRef にすると「再レンダーされないせいでUIが古い値を持ち続ける」ことがあります。
    • 「画面に出す値は state、裏で持っておきたい値は ref」と意識的に使い分けが大事です。

まとめ

  • useRef「再レンダーを引き起こさずに値を保持する箱」

  • 主な用途は

    1. DOM要素へのアクセス(フォーカス、スクロールなど)
    2. 再レンダー不要な値の保持(前回の値、setIntervalのIDなど)
  • useState との違いは 「UIに反映したいなら state、裏で保持したいなら ref」

  • 注意点は「画面に反映されない」「初期値は null」「乱用しない」

1
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?