0
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?

⏱️ useRef × useEffectで定期実行処理を実装する方法

Last updated at Posted at 2025-06-01

はじめに

Reactで定期的に何かを実行したいとき、あなたはどうしていますか?

setInterval(() => {
  console.log("毎秒実行");
}, 1000);

このような処理をコンポーネント内に直接書くと、バグやメモリリークの原因になります。
そんなときに使えるのが、useRefuseEffectを組み合わせた副作用の安全な制御方法です。

🎯 本記事では以下をわかりやすく紹介します:

  • useRefとは何か、なぜ使うのか
  • useEffectとの組み合わせ方
  • 最小構成の「カウントアップタイマー」実装

useRefとは?

useRef は主に DOMを参照するためのフックですが、実はそれだけではありません。

const intervalRef = useRef<NodeJS.Timeout | null>(null);

このように書くことで、再レンダリングされても値が保持される「永続的な変数」として使えます。

  • useState:変更すると再描画が走る
  • useRef:変更しても再描画されない(非表示の箱)

useEffectとは?

useEffect は「副作用(Effect)」を扱うためのフックです。

副作用とは、例えば以下のような処理です:

  • データの取得(fetch)
  • イベントリスナーの登録
  • タイマー(setInterval, setTimeout)
  • 外部ライブラリとの連携

Reactのレンダリングとは直接関係ない処理を、特定のタイミングで実行したいときに使います。

[] は「初期処理」

useEffect(() => {
  // この中の処理はコンポーネントが表示されたときに実行されるよ
}, []);

第二引数の「依存配列」が空([])だと、初回マウント時のみ実行されるという意味になります。

🧹 return () => { ... } は「お片付け処理」

さらに、useEffect の中で return を書くと、

useEffect(() => {
  return () => {
    // コンポーネントが画面から消える(アンマウントされる)ときに実行される
  };
}, []);

以下のようなタイマーを作っていたとします:

intervalRef.current = setInterval(() => {
  setCount((c) => c + 1);
}, 1000);

これを放っておくと、コンポーネントが画面から消えてもずっと裏で実行され続けてしまうため、メモリリークや予期せぬ動作になります。

なので、「消えるときに止める(お片付け)」が必要になります。

useEffect(() => {
  // mount(表示)時の処理:今回はなし

  return () => {
    // unmount(非表示になるとき)にやりたいことを書く
    stop(); // → タイマーを止めるなど
  };
}, []);

このように書くことで、

  • コンポーネントが**マウントされたとき(表示されたとき)**は何もしない
  • コンポーネントが**アンマウントされるとき(画面から消えるとき)**に stop() を呼んで、タイマーを止める

という安全な流れが完成します。
「使い終わったあとにちゃんと片付ける」が useEffectreturn の役割です。

useEffect × useRef 実装例

まずは、setInterval を安全に使う基本パターンを紹介します。

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

export default function Timer() {
  const [count, setCount] = useState(0);
  const intervalRef = useRef<NodeJS.Timeout | null>(null);

  const start = () => {
    if (intervalRef.current !== null) return; // 二重防止
    intervalRef.current = setInterval(() => {
      setCount((prev) => prev + 1);
    }, 1000);
  };

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

  const reset = () => {
    stop();
    setCount(0);
  };

  useEffect(() => {
    return () => {
      stop(); // コンポーネントが消えるときに停止
    };
  }, []);

  return (
    <div>
      <p>⏱️ Count: {count}</p>
      <button onClick={start}>▶️ Start</button>
      <button onClick={stop}>⏹ Stop</button>
      <button onClick={reset}>🔁 Reset</button>
    </div>
  );
}

ポイント

  • intervalRef.current にタイマーIDを保存
  • useEffect のクリーンアップで自動停止
  • 状態と副作用を分離して管理できる
目的 適したフック
値を保持したいが再描画は不要 useRef
副作用の実行/終了タイミングを制御 useEffect
状態管理と画面更新 useState

setIntervalsetTimeout を扱うときは、必ずuseRefでIDを保持し、useEffectで開始・終了を管理するのがベストプラクティスです。

おわりに

  • Reactでタイマーや自動再生を扱いたい方
  • useRefの活用方法を知りたい方
  • Game of Lifeのような時間ベースのアプリを作りたい方

にとって、useRef + useEffect の組み合わせは非常に強力な武器になります。

ぜひ一度、実際に試してみてください!

0
0
0

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
0
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?