ReactのuseRefを活用したタイマーとアニメーション制御の完全ガイド

Reactにおける開発では、効率的な状態管理や動的な要素の操作が求められます。特にタイマーやアニメーションの制御では、再レンダリングによるパフォーマンスの低下や意図しない挙動を防ぐための工夫が必要です。本記事では、ReactのuseRefフックを活用して、これらの課題を解決する方法を解説します。初心者でもわかりやすいコード例を用いて、タイマーやアニメーション制御の基本から応用までをカバーします。

目次

useRefの基本概念と利点

ReactのuseRefは、DOM要素やミューテーブルな値を保持するためのフックです。再レンダリング時にも値が保持される特徴を持ち、状態の管理や直接的なDOM操作に活用されます。

useRefの基本概念

useRefを使うことで、値の変更がコンポーネントの再レンダリングを引き起こさない形でデータを保持できます。この性質は、次のような場面で特に有用です:

  • タイマーIDやアニメーションの進行状況の保存
  • DOMノードへの直接アクセス
  • 過去の値の参照

useRefの利点

  1. 再レンダリングを防ぐ
    useRefで保持した値が変化しても再レンダリングは発生しません。そのため、パフォーマンスへの影響を抑えることができます。
  2. ミューテーブルな値の管理
    useRefはミューテーブルなオブジェクトを返すため、必要に応じて簡単に更新できます。
  3. DOM操作が容易
    HTML要素を直接参照できるため、アニメーションやフォーカス管理などで便利です。

useRefのシンプルな例

以下はuseRefの基本的な使用例です:

import React, { useRef } from "react";

function FocusInput() {
  const inputRef = useRef(null);

  const handleFocus = () => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleFocus}>Focus Input</button>
    </div>
  );
}

export default FocusInput;

この例では、useRefを使って<input>要素への参照を保持し、ボタンクリック時にフォーカスを設定しています。

useRefは単なる値保持だけでなく、リアクティブな機能を活用せずに効率的な操作を可能にする強力なツールです。

タイマー制御にuseRefを活用する理由

タイマーやインターバルを扱う際、Reactのコンポーネント再レンダリングやクリーンアップの影響を最小限に抑えることが重要です。useRefは、このような場面で非常に役立つツールとなります。

Reactでタイマー管理が難しい理由

タイマーを制御する場合、setTimeoutsetIntervalを使用しますが、以下のような課題が発生します:

  • タイマーIDの保持: setTimeoutsetIntervalが返すタイマーIDを適切に管理しなければ、意図しない動作やリソースリークの原因となります。
  • 再レンダリングによるリセット: useStateでタイマーIDを管理すると、再レンダリング時に新しい値で上書きされる可能性があります。

useRefを使うことでこれらの問題を解消し、効率的なタイマー制御が可能になります。

useRefをタイマー管理に使う利点

  1. 値の永続化: 再レンダリングによって値が失われることがないため、タイマーIDを安全に保持できます。
  2. 柔軟な更新: useRefの値はミューテーブルであるため、簡単に更新が可能です。
  3. クリーンなクリーンアップ: コンポーネントのアンマウント時にタイマーを確実に解除するための仕組みを簡単に実装できます。

useRefを利用したタイマー管理の例

以下はuseRefを活用したタイマー制御の例です:

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

function TimerComponent() {
  const [count, setCount] = useState(0);
  const timerRef = useRef(null);

  const startTimer = () => {
    if (timerRef.current) return; // 既存のタイマーがあれば何もしない
    timerRef.current = setInterval(() => {
      setCount((prevCount) => prevCount + 1);
    }, 1000);
  };

  const stopTimer = () => {
    if (timerRef.current) {
      clearInterval(timerRef.current);
      timerRef.current = null;
    }
  };

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={startTimer}>Start Timer</button>
      <button onClick={stopTimer}>Stop Timer</button>
    </div>
  );
}

export default TimerComponent;

このコードでは、timerRefがタイマーIDを保持し、startTimerstopTimerの間で安全に操作されます。

タイマー制御にuseRefを使用するメリット

  • タイマーの多重設定を防止
  • リソースリークを防ぐ確実なクリーンアップ
  • 再レンダリングの影響を受けずに安定動作

タイマーを効率的に管理したい場合、useRefは非常に効果的な選択肢となります。

useRefを使用したタイマー実装の例

useRefを使うことで、タイマーの開始、停止、リセットを簡単かつ安全に実装できます。ここでは、実際のコードを用いて、タイマーの構築手順を具体的に解説します。

基本的なタイマー実装

以下は、useRefを使用してスタート、ストップ、リセットが可能なシンプルなタイマーを実装した例です:

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

function TimerWithReset() {
  const [time, setTime] = useState(0);
  const timerRef = useRef(null);

  const startTimer = () => {
    if (timerRef.current) return; // 既にタイマーが動作中の場合は何もしない
    timerRef.current = setInterval(() => {
      setTime((prevTime) => prevTime + 1);
    }, 1000); // 1秒ごとに更新
  };

  const stopTimer = () => {
    if (timerRef.current) {
      clearInterval(timerRef.current); // タイマーを停止
      timerRef.current = null; // リファレンスをリセット
    }
  };

  const resetTimer = () => {
    stopTimer(); // タイマーを停止
    setTime(0); // 時間をリセット
  };

  return (
    <div>
      <h1>Timer: {time} seconds</h1>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
      <button onClick={resetTimer}>Reset</button>
    </div>
  );
}

export default TimerWithReset;

コード解説

  • timerRef.currentの確認
    タイマーが既に動作中の場合、新しいタイマーを設定しないようにしています。
  • clearIntervalによる停止
    タイマーを停止してメモリリークを防ぎます。
  • リセット機能
    stopTimerを呼び出してから時間をリセットすることで、確実に初期状態に戻します。

リアルタイムのデジタル時計の例

useRefを応用して、現在時刻をリアルタイムで表示するデジタル時計も作成できます。

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

function RealTimeClock() {
  const [time, setTime] = useState(new Date().toLocaleTimeString());
  const timerRef = useRef(null);

  useEffect(() => {
    timerRef.current = setInterval(() => {
      setTime(new Date().toLocaleTimeString());
    }, 1000);

    return () => {
      clearInterval(timerRef.current); // クリーンアップ
    };
  }, []);

  return (
    <div>
      <h1>Current Time: {time}</h1>
    </div>
  );
}

export default RealTimeClock;

コード解説

  • useEffectの使用
    タイマーをコンポーネントのマウント時に開始し、アンマウント時に停止します。
  • リアルタイムの更新
    setIntervalを使って1秒ごとに現在時刻を更新します。

タイマー実装のポイント

  1. useRefでタイマーIDを管理し、メモリリークを防ぐ。
  2. 必要に応じてクリーンアップ処理を実装する。
  3. コードをシンプルかつ可読性の高い状態に保つ。

useRefを活用することで、Reactにおけるタイマー実装を効率的かつ安全に行えます。

アニメーション制御におけるuseRefの応用

Reactアプリケーションでスムーズなアニメーションを実現するためには、DOM要素や状態を効率的に操作する必要があります。useRefを使用することで、アニメーションのパフォーマンスを向上させつつ、コードの簡潔性も保てます。

アニメーションにおける課題

  1. 再レンダリングの影響
    アニメーション中に状態管理をuseStateで行うと、レンダリングごとに状態が更新され、パフォーマンスが低下する可能性があります。
  2. 連続したアニメーションの管理
    複数のアニメーションをシームレスに制御する際、タイミングや状態管理が複雑になります。
  3. 外部ライブラリの依存軽減
    外部ライブラリを使わずに、Reactの機能でアニメーションを制御したい場合に工夫が必要です。

useRefを活用する利点

  • DOM要素を直接操作するため、状態更新による再レンダリングを避けられる。
  • アニメーションの進行状況やタイマーを効率的に管理できる。
  • パフォーマンスが向上し、ユーザー体験を改善。

useRefを使ったアニメーションの例

以下は、CSSアニメーションをJavaScriptで制御する例です:

import React, { useRef } from "react";

function AnimationExample() {
  const boxRef = useRef(null);

  const startAnimation = () => {
    if (boxRef.current) {
      boxRef.current.style.transition = "transform 1s ease-in-out";
      boxRef.current.style.transform = "translateX(200px)";
    }
  };

  const resetAnimation = () => {
    if (boxRef.current) {
      boxRef.current.style.transition = "none";
      boxRef.current.style.transform = "translateX(0)";
    }
  };

  return (
    <div>
      <div
        ref={boxRef}
        style={{
          width: "100px",
          height: "100px",
          backgroundColor: "blue",
          margin: "20px",
        }}
      ></div>
      <button onClick={startAnimation}>Start Animation</button>
      <button onClick={resetAnimation}>Reset Animation</button>
    </div>
  );
}

export default AnimationExample;

コード解説

  1. boxRefの使用
    DOM要素への直接アクセスにuseRefを使用しています。
  2. スタイルの変更
    JavaScriptでstyle.transitionstyle.transformを操作して、CSSアニメーションを動的に変更しています。
  3. アニメーションのリセット
    トランジションを一旦無効にすることでアニメーションをリセットしています。

useRefとrequestAnimationFrameの組み合わせ

より精細な制御が必要な場合、requestAnimationFrameと組み合わせて滑らかなアニメーションを実現できます。

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

function SmoothAnimation() {
  const boxRef = useRef(null);
  const animationRef = useRef(null);

  const animate = () => {
    if (boxRef.current) {
      const currentLeft = parseFloat(boxRef.current.style.left || "0");
      if (currentLeft < 300) {
        boxRef.current.style.left = `${currentLeft + 2}px`;
        animationRef.current = requestAnimationFrame(animate);
      }
    }
  };

  const startAnimation = () => {
    if (!animationRef.current) {
      animationRef.current = requestAnimationFrame(animate);
    }
  };

  const stopAnimation = () => {
    if (animationRef.current) {
      cancelAnimationFrame(animationRef.current);
      animationRef.current = null;
    }
  };

  useEffect(() => {
    return () => stopAnimation(); // クリーンアップ
  }, []);

  return (
    <div>
      <div
        ref={boxRef}
        style={{
          width: "50px",
          height: "50px",
          backgroundColor: "red",
          position: "absolute",
          left: "0px",
          top: "50px",
        }}
      ></div>
      <button onClick={startAnimation}>Start Animation</button>
      <button onClick={stopAnimation}>Stop Animation</button>
    </div>
  );
}

export default SmoothAnimation;

コード解説

  • requestAnimationFrameの利用
    タイマーよりも滑らかなアニメーションを提供するブラウザAPIを利用しています。
  • useRefによる状態管理
    アニメーションIDをanimationRefに保持し、再レンダリングの影響を受けない形で管理します。

アニメーション実装のポイント

  1. useRefでDOM要素や進行状況を管理し、パフォーマンスを向上。
  2. 必要に応じてrequestAnimationFrameを活用。
  3. クリーンアップ処理を忘れずに実装。

useRefを活用することで、再レンダリングの影響を受けずにスムーズなアニメーションを実現できます。

アニメーション用のコード例と実装手順

useRefを利用したアニメーションのコード例を示し、実際にどのように構築するか、その手順を解説します。このセクションでは、シンプルな動きから複雑なアニメーションまで段階的に紹介します。

ステップ1: シンプルなアニメーション

以下は、ボタンをクリックすると要素が動くシンプルなアニメーション例です:

import React, { useRef } from "react";

function SimpleAnimation() {
  const boxRef = useRef(null);

  const handleMove = () => {
    if (boxRef.current) {
      boxRef.current.style.transition = "all 0.5s ease";
      boxRef.current.style.transform = "translateY(100px)";
    }
  };

  return (
    <div>
      <div
        ref={boxRef}
        style={{
          width: "100px",
          height: "100px",
          backgroundColor: "green",
          margin: "20px",
        }}
      ></div>
      <button onClick={handleMove}>Move Box</button>
    </div>
  );
}

export default SimpleAnimation;

手順

  1. useRefで対象のDOM要素を参照。
  2. スタイルのtransitiontransformを設定してアニメーションを適用。
  3. イベントハンドラーをトリガーとしてアニメーションを開始。

ステップ2: 繰り返し動作するアニメーション

次に、クリックで繰り返し動作するアニメーションを作成します。

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

function LoopAnimation() {
  const boxRef = useRef(null);
  const [direction, setDirection] = useState(1);

  const handleMove = () => {
    if (boxRef.current) {
      const newY = direction === 1 ? 100 : 0;
      boxRef.current.style.transition = "all 0.5s ease";
      boxRef.current.style.transform = `translateY(${newY}px)`;
      setDirection(direction * -1);
    }
  };

  return (
    <div>
      <div
        ref={boxRef}
        style={{
          width: "100px",
          height: "100px",
          backgroundColor: "blue",
          margin: "20px",
        }}
      ></div>
      <button onClick={handleMove}>Toggle Move</button>
    </div>
  );
}

export default LoopAnimation;

手順

  1. useStateで動きの方向を管理。
  2. setDirectionでクリックごとに方向を切り替え。
  3. transformで新しい位置を設定。

ステップ3: requestAnimationFrameを用いたスムーズなアニメーション

もっと高度なアニメーションにはrequestAnimationFrameを使用します。

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

function SmoothBounceAnimation() {
  const boxRef = useRef(null);
  const animationFrameRef = useRef(null);
  const positionRef = useRef(0);
  const directionRef = useRef(1);

  const animate = () => {
    if (boxRef.current) {
      positionRef.current += directionRef.current * 2;
      if (positionRef.current > 200 || positionRef.current < 0) {
        directionRef.current *= -1;
      }
      boxRef.current.style.transform = `translateY(${positionRef.current}px)`;
      animationFrameRef.current = requestAnimationFrame(animate);
    }
  };

  const startAnimation = () => {
    if (!animationFrameRef.current) {
      animationFrameRef.current = requestAnimationFrame(animate);
    }
  };

  const stopAnimation = () => {
    if (animationFrameRef.current) {
      cancelAnimationFrame(animationFrameRef.current);
      animationFrameRef.current = null;
    }
  };

  useEffect(() => {
    return () => stopAnimation();
  }, []);

  return (
    <div>
      <div
        ref={boxRef}
        style={{
          width: "50px",
          height: "50px",
          backgroundColor: "red",
          position: "absolute",
          top: "50px",
          left: "50px",
        }}
      ></div>
      <button onClick={startAnimation}>Start Animation</button>
      <button onClick={stopAnimation}>Stop Animation</button>
    </div>
  );
}

export default SmoothBounceAnimation;

手順

  1. requestAnimationFrameでアニメーションを管理
    軽量かつスムーズな動作を実現。
  2. 状態をuseRefで管理
    positionRefdirectionRefを用いて進行と方向を制御。
  3. クリーンアップ処理の実装
    useEffectでコンポーネントのアンマウント時にアニメーションを停止。

実装のポイント

  • 効率性を追求
    DOM操作はuseRefを使い最小限に。
  • スムーズな動作
    繰り返し動作やリアルタイム更新を考慮。
  • 安全性
    コンポーネントが削除される際にアニメーションを確実に停止。

このようにuseRefを活用すれば、シンプルなものから高度なアニメーションまで柔軟に対応できます。

useRefとuseEffectの組み合わせのポイント

Reactでは、useRefuseEffectを組み合わせることで、DOM操作やタイマー、アニメーションなどの状態管理を強力に制御できます。このセクションでは、それぞれの役割と効果的な使い方のポイントを解説します。

useRefとuseEffectの役割

  • useRefの役割
    DOMノードやミューテーブルな値を保持し、再レンダリングの影響を受けずにデータを参照または更新するために使用します。
  • useEffectの役割
    副作用(サイドエフェクト)を処理するためのフックです。初回レンダリング時や特定の依存関係の変更時に特定の処理を実行できます。

これらを組み合わせることで、DOM要素の初期化、リソースの設定やクリーンアップ、タイマーの開始や停止などを柔軟に実装できます。

useRefとuseEffectを組み合わせる場面

  1. DOM操作の初期化
    useRefで取得したDOMノードを、コンポーネントのマウント後に初期化。
  2. タイマーやインターバルの管理
    useRefでタイマーIDを保持し、useEffectで開始・停止のロジックを実装。
  3. リソースのクリーンアップ
    useEffectのクリーンアップ関数でリソースを解放し、メモリリークを防止。

基本的な実装例

以下は、DOMノードの初期化とクリーンアップを行う例です。

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

function FocusInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    // コンポーネントのマウント時にフォーカスを設定
    if (inputRef.current) {
      inputRef.current.focus();
    }

    // クリーンアップ処理
    return () => {
      console.log("Component unmounted");
    };
  }, []); // 依存配列が空なので、マウントとアンマウント時にのみ実行

  return <input ref={inputRef} type="text" />;
}

export default FocusInput;

コード解説

  • DOMノードへの参照
    useRefで取得したDOM要素に初期フォーカスを設定。
  • クリーンアップ関数
    コンポーネントのアンマウント時に動作する処理を記述。

タイマー管理の実装例

タイマーの管理にもuseRefとuseEffectの組み合わせが有効です。

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

function TimerComponent() {
  const [count, setCount] = useState(0);
  const timerRef = useRef(null);

  useEffect(() => {
    timerRef.current = setInterval(() => {
      setCount((prev) => prev + 1);
    }, 1000);

    // クリーンアップ処理
    return () => {
      clearInterval(timerRef.current);
    };
  }, []); // マウント時にのみ実行

  return <h1>Count: {count}</h1>;
}

export default TimerComponent;

コード解説

  • useRefでタイマーIDを保持
    setIntervalのIDをtimerRef.currentに格納。
  • クリーンアップ関数で停止
    clearIntervalを実行してリソースリークを防ぐ。

ポイントと注意点

  1. 依存配列を正しく設定
    useEffect内の依存配列を正しく設定し、不要な再実行を防ぎます。
  2. クリーンアップを必ず実装
    タイマーやイベントリスナーは必ずクリーンアップし、リソースリークを防ぎます。
  3. useRefでレンダリングを制御
    useRefを使うことで再レンダリングを抑え、パフォーマンスを向上させます。

useRefとuseEffectを効果的に組み合わせることで、Reactアプリケーションの管理が格段に効率化します。

具体的な課題とその解決方法

useRefを利用したタイマーやアニメーションの実装では、開発中にいくつかの課題に直面することがあります。このセクションでは、代表的な課題とその解決方法を具体例とともに解説します。

課題1: タイマーの多重起動

タイマーを管理する際、setIntervalsetTimeoutが多重に起動してしまう問題はよくあります。これにより、予期しない挙動やパフォーマンスの低下が発生します。

解決方法: タイマー状態をuseRefで管理

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

function MultiTimerGuard() {
  const [count, setCount] = useState(0);
  const timerRef = useRef(null);

  const startTimer = () => {
    if (timerRef.current) return; // タイマーが既に動作している場合は何もしない
    timerRef.current = setInterval(() => {
      setCount((prev) => prev + 1);
    }, 1000);
  };

  const stopTimer = () => {
    if (timerRef.current) {
      clearInterval(timerRef.current);
      timerRef.current = null; // タイマー状態をリセット
    }
  };

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
    </div>
  );
}

export default MultiTimerGuard;

ポイント

  • タイマーが動作中かどうかをtimerRef.currentで判定し、重複を防ぎます。
  • 停止時にリセットすることで、再起動が安全に行えます。

課題2: クリーンアップ漏れによるメモリリーク

タイマーやアニメーションのリソースが解放されず、メモリリークを引き起こす問題が発生することがあります。

解決方法: useEffectでクリーンアップを実装

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

function TimerWithCleanup() {
  const [count, setCount] = useState(0);
  const timerRef = useRef(null);

  useEffect(() => {
    timerRef.current = setInterval(() => {
      setCount((prev) => prev + 1);
    }, 1000);

    // クリーンアップ処理
    return () => {
      clearInterval(timerRef.current);
    };
  }, []); // マウント時のみ実行

  return <h1>Count: {count}</h1>;
}

export default TimerWithCleanup;

ポイント

  • useEffectのクリーンアップ関数でタイマーを確実に停止します。
  • アンマウント時のリソース解放を徹底することで、不要な動作を防ぎます。

課題3: 再レンダリングによるアニメーションの中断

アニメーション実行中にコンポーネントが再レンダリングされると、意図しない中断やちらつきが発生することがあります。

解決方法: useRefで状態を保持

import React, { useRef } from "react";

function StableAnimation() {
  const boxRef = useRef(null);

  const startAnimation = () => {
    if (boxRef.current) {
      boxRef.current.style.transition = "transform 1s ease-in-out";
      boxRef.current.style.transform = "translateX(200px)";
    }
  };

  return (
    <div>
      <div
        ref={boxRef}
        style={{
          width: "100px",
          height: "100px",
          backgroundColor: "blue",
          margin: "20px",
        }}
      ></div>
      <button onClick={startAnimation}>Start Animation</button>
    </div>
  );
}

export default StableAnimation;

ポイント

  • アニメーションの状態をuseRefで管理し、再レンダリングの影響を受けないようにします。

課題4: 高度なアニメーションのタイミング管理

複雑なアニメーションでは、タイミングのずれが発生する場合があります。

解決方法: requestAnimationFrameの活用

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

function SmoothAnimation() {
  const boxRef = useRef(null);
  const animationRef = useRef(null);

  const animate = (timestamp) => {
    if (boxRef.current) {
      const progress = (timestamp % 2000) / 2000;
      const translateX = 200 * Math.sin(progress * 2 * Math.PI);
      boxRef.current.style.transform = `translateX(${translateX}px)`;
    }
    animationRef.current = requestAnimationFrame(animate);
  };

  useEffect(() => {
    animationRef.current = requestAnimationFrame(animate);

    // クリーンアップ
    return () => {
      cancelAnimationFrame(animationRef.current);
    };
  }, []);

  return (
    <div>
      <div
        ref={boxRef}
        style={{
          width: "50px",
          height: "50px",
          backgroundColor: "red",
          position: "absolute",
        }}
      ></div>
    </div>
  );
}

export default SmoothAnimation;

ポイント

  • タイミング管理にはrequestAnimationFrameを使用。
  • アニメーションフレームをuseRefで管理し、パフォーマンスを最適化。

まとめ

useRefを活用することで、以下の課題を解決できます:

  1. タイマーの多重起動を防ぐ。
  2. クリーンアップを実装し、メモリリークを回避。
  3. 再レンダリングの影響を受けないアニメーションを構築。
  4. 高度なアニメーションのタイミングをスムーズに制御。

これらのテクニックを使いこなすことで、安定したReactアプリケーションを構築できます。

応用例: カスタムフックを作成する

useRefとその他のReactフックを組み合わせることで、汎用性の高いカスタムフックを作成できます。これにより、コードの再利用性が向上し、タイマーやアニメーションの制御が簡単になります。

タイマー用のカスタムフック

以下は、スタート、ストップ、リセットが可能なタイマーを提供するカスタムフックの例です。

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

function useTimer() {
  const [time, setTime] = useState(0);
  const timerRef = useRef(null);

  const start = () => {
    if (timerRef.current) return; // 既にタイマーが動作中なら何もしない
    timerRef.current = setInterval(() => {
      setTime((prevTime) => prevTime + 1);
    }, 1000);
  };

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

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

  useEffect(() => {
    return () => stop(); // クリーンアップ
  }, []);

  return { time, start, stop, reset };
}

export default useTimer;

カスタムフックの使用例

import React from "react";
import useTimer from "./useTimer";

function TimerComponent() {
  const { time, start, stop, reset } = useTimer();

  return (
    <div>
      <h1>Time: {time} seconds</h1>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

export default TimerComponent;

ポイント

  • useTimerフックで、タイマーのスタート、ストップ、リセットのロジックを一元管理。
  • 再利用可能なコードで、複数のコンポーネントに簡単に組み込むことが可能。

アニメーション用のカスタムフック

次に、requestAnimationFrameを利用したアニメーション制御用のカスタムフックを作成します。

import { useRef, useEffect } from "react";

function useAnimation(callback) {
  const animationRef = useRef(null);

  const start = () => {
    if (!animationRef.current) {
      const animate = (time) => {
        callback(time);
        animationRef.current = requestAnimationFrame(animate);
      };
      animationRef.current = requestAnimationFrame(animate);
    }
  };

  const stop = () => {
    if (animationRef.current) {
      cancelAnimationFrame(animationRef.current);
      animationRef.current = null;
    }
  };

  useEffect(() => {
    return () => stop(); // クリーンアップ
  }, []);

  return { start, stop };
}

export default useAnimation;

カスタムフックの使用例

import React, { useRef } from "react";
import useAnimation from "./useAnimation";

function AnimatedBox() {
  const boxRef = useRef(null);
  const position = useRef(0);

  const { start, stop } = useAnimation((time) => {
    if (boxRef.current) {
      position.current = (position.current + 1) % 200; // アニメーションの位置を計算
      boxRef.current.style.transform = `translateX(${position.current}px)`;
    }
  });

  return (
    <div>
      <div
        ref={boxRef}
        style={{
          width: "50px",
          height: "50px",
          backgroundColor: "blue",
          position: "absolute",
          top: "50px",
        }}
      ></div>
      <button onClick={start}>Start Animation</button>
      <button onClick={stop}>Stop Animation</button>
    </div>
  );
}

export default AnimatedBox;

ポイント

  • useAnimationは汎用的に利用できるように、コールバック関数を引数として受け取ります。
  • アニメーションロジックはコンポーネントごとにカスタマイズ可能。

応用例: タイマーとアニメーションの組み合わせ

タイマーとアニメーションのカスタムフックを組み合わせて、アニメーションを一定時間で停止するような応用も可能です。

import React from "react";
import useTimer from "./useTimer";
import useAnimation from "./useAnimation";

function AnimatedTimerBox() {
  const { time, start: startTimer, stop: stopTimer, reset } = useTimer();
  const position = useRef(0);
  const boxRef = useRef(null);

  const { start: startAnimation, stop: stopAnimation } = useAnimation(() => {
    if (boxRef.current) {
      position.current = time * 10; // タイマーを基準にアニメーション
      boxRef.current.style.transform = `translateX(${position.current}px)`;
    }
  });

  const start = () => {
    startTimer();
    startAnimation();
  };

  const stop = () => {
    stopTimer();
    stopAnimation();
  };

  return (
    <div>
      <div
        ref={boxRef}
        style={{
          width: "50px",
          height: "50px",
          backgroundColor: "green",
          position: "absolute",
        }}
      ></div>
      <h1>Time: {time} seconds</h1>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

export default AnimatedTimerBox;

カスタムフック作成のメリット

  1. コードの再利用性が向上
    汎用的なロジックを1つのフックにまとめることで、複数のコンポーネントで活用可能。
  2. 開発効率の向上
    一度作成したカスタムフックを利用すれば、同様の機能を迅速に実装可能。
  3. 保守性の向上
    ロジックが一元化されるため、変更やバグ修正が容易。

カスタムフックを活用することで、より効率的で柔軟なReactアプリケーション開発が実現します。

まとめ

本記事では、ReactにおけるuseRefを活用したタイマーとアニメーション制御について解説しました。useRefの基本概念からタイマーやアニメーションの具体的な実装例、さらにカスタムフックを作成する応用例まで幅広く取り上げました。

useRefを利用することで、再レンダリングの影響を受けない状態管理が可能になり、効率的なタイマー制御やスムーズなアニメーションを実現できます。また、カスタムフックを導入することで、コードの再利用性を高め、保守性の向上にもつなげられます。

useRefは単なるDOM操作の補助ツールではなく、React開発の可能性を広げる強力なツールです。本記事を参考に、さらに高度なReactアプリケーションの構築に挑戦してください。

コメント

コメントする

目次