ReactのuseEffectでタイマー処理を実装する方法を徹底解説!初心者でもできるReactフック入門
生徒
「Reactで時間が経つごとに表示を変えるタイマーって作れるんですか?」
先生
「ReactではuseEffectというフックを使うと、タイマー処理を簡単に実装できますよ。」
生徒
「フックって何ですか?そして、どうやって時間を動かすんですか?」
先生
「フックはReactで便利な仕組みのことです。useEffectを使えば、一定の間隔で処理を繰り返すこともできます。では具体的に見ていきましょう!」
1. useEffectとは?
useEffectはReactのフックのひとつで、コンポーネントが画面に表示されたときや、データが変わったときに「副作用(サイドエフェクト)」を実行するために使います。副作用とは、データを読み込んだり、ログを出力したり、タイマーを動かしたりするような「画面の描画以外の処理」のことです。
例えば、「1秒ごとに数字を増やすカウントアップタイマー」を作るとき、useEffectを利用すると簡単にできます。
2. useEffectでタイマー処理を作る基本の流れ
タイマー処理をReactで実装する手順は次の通りです。
useStateで時間やカウントを管理するuseEffectの中でsetIntervalを使って一定間隔ごとの処理を設定する- コンポーネントが消えるときに
clearIntervalでタイマーを止める
これをしないと、タイマーが動き続けてメモリを使いすぎたり、二重に処理が走ったりしてしまうので注意が必要です。
3. 実際のサンプルコード
それでは、実際に「1秒ごとにカウントアップするタイマー」を作るプログラムを書いてみましょう。
import React, { useState, useEffect } from "react";
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
// 1秒ごとにcountを1増やす処理
const timer = setInterval(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
// コンポーネントが消えたときにタイマーを止める
return () => clearInterval(timer);
}, []);
return (
<div>
<h1>タイマー: {count} 秒</h1>
</div>
);
}
export default Timer;
4. useEffectで注意するポイント
初心者がつまずきやすいポイントとして、タイマーを止める処理を忘れるケースがあります。Reactではコンポーネントが画面から消えるときに「後片付け(クリーンアップ処理)」をする必要があります。これをuseEffectの中でreturn () => { ... }と書くことで実現できます。
もしこれをしないと、不要になったタイマーがずっと動き続け、アプリが重くなる原因になります。
5. 実用的な応用例
タイマー処理はゲームやストップウォッチ、残り時間のカウントダウンなどにも使われます。例えば「10秒からカウントダウンして0になったら終了メッセージを表示する」処理もuseEffectで簡単に書けます。
import React, { useState, useEffect } from "react";
function Countdown() {
const [time, setTime] = useState(10);
useEffect(() => {
if (time <= 0) return;
const timer = setInterval(() => {
setTime((prevTime) => prevTime - 1);
}, 1000);
return () => clearInterval(timer);
}, [time]);
return (
<div>
{time > 0 ? <h1>残り時間: {time} 秒</h1> : <h1>時間切れです!</h1>}
</div>
);
}
export default Countdown;
6. useEffectとsetTimeoutの違い
似たような関数にsetTimeoutがあります。これは「一度だけ時間が経ったら処理を実行する」仕組みです。対してsetIntervalは「一定間隔で繰り返す」仕組みです。
例えば「5秒後にメッセージを表示する」場合はsetTimeoutを使い、「1秒ごとに数字を増やす」場合はsetIntervalを使う、というふうに場面に応じて使い分けます。
まとめ
ここまで、Reactの「useEffect」フックを活用したタイマー処理の実装方法について、基礎から応用まで詳しく解説してきました。React開発において、時間の経過とともに表示を更新する機能は非常に頻繁に登場します。ストップウォッチ、カウントダウンタイマー、さらには自動スライドショーの制御など、その用途は多岐にわたります。初心者の方が最初につまずきやすいポイントは「状態管理(useState)」と「副作用(useEffect)」の連携、そして何より「クリーンアップ処理」の重要性です。
useEffectによるタイマー実装の核心
Reactのコンポーネントは、状態が変わるたびに再レンダリング(再描画)されます。しかし、setIntervalのようなブラウザのタイマー機能はReactの外側の世界で動くものです。そのため、Reactのライフサイクルに合わせて正しく制御してあげないと、予期せぬ挙動やメモリリークを引き起こしてしまいます。今回の学習で最も重要なのは、useEffectの戻り値としてclearIntervalを実行し、コンポーネントが破棄される際にタイマーを確実に止めるというルールを徹底することです。
状態更新の注意点:関数型アップデート
タイマーの中で現在のカウントを更新する場合、setCount(count + 1)とするのではなく、setCount((prev) => prev + 1)という「関数型アップデート」を使うのがベストプラクティスです。これにより、クロージャの問題を回避し、常に最新のステートに基づいた計算が可能になります。これはJavaScriptの非同期処理とReactのステート更新の仕組みを深く理解する第一歩でもあります。
実践的な活用例:複雑なタイマー制御
さらに一歩進んだ実装として、タイマーの「開始」「停止」「リセット」を切り替える機能を備えたサンプルコードを振り返ってみましょう。これらを組み合わせることで、より実用的なアプリケーションに近い形に仕上げることができます。
import React, { useState, useEffect } from "react";
function AdvancedTimer() {
const [seconds, setSeconds] = useState(0);
const [isActive, setIsActive] = useState(false);
useEffect(() => {
let interval = null;
if (isActive) {
// タイマーがアクティブな時だけ動かす
interval = setInterval(() => {
setSeconds((prevSeconds) => prevSeconds + 1);
}, 1000);
} else if (!isActive && seconds !== 0) {
// 一時停止した時の処理
clearInterval(interval);
}
// クリーンアップ関数で確実に停止
return () => clearInterval(interval);
}, [isActive, seconds]);
const toggle = () => setIsActive(!isActive);
const reset = () => {
setSeconds(0);
setIsActive(false);
};
return (
<div className="text-center p-4">
<h3 className="mb-3">高度なタイマー</h3>
<div className="display-4 mb-3">{seconds}秒</div>
<div>
<button
className={`btn ${isActive ? "btn-warning" : "btn-success"} me-2`}
onClick={toggle}
>
{isActive ? "一時停止" : "開始"}
</button>
<button className="btn btn-danger" onClick={reset}>
リセット
</button>
</div>
</div>
);
}
export default AdvancedTimer;
このように、useEffectの依存配列(第2引数の [])を適切に設定することで、特定のステートが変化したタイミングでタイマーを再始動させたり、停止させたりといった柔軟な制御が可能になります。モダンなReact開発においては、クラスコンポーネント時代のライフサイクルメソッドよりも、フックを使った宣言的な記述が主流です。
最初は難しく感じるかもしれませんが、自分でコードを書き換えながら「依存配列に何を入れればどう動くか」を実験してみるのが上達の近道です。ぜひ、自分だけのカスタムタイマーを作って、Reactの強力な機能を体感してみてください。
生徒
「先生、ありがとうございました!useEffectの中でタイマーを動かす仕組みが、なんとなく掴めてきました。でも、最後に書いてあった『クリーンアップ』っていうのがやっぱり一番大事なんですね。」
先生
「その通りです!もしクリーンアップを忘れてしまうと、画面が切り替わっても裏側でタイマーがずっと走り続けて、どんどんメモリを食いつぶしちゃうんです。これを『メモリリーク』と呼ぶのですが、アプリが重くなる原因のナンバーワンと言っても過言ではありませんよ。」
生徒
「なるほど…。目に見えないからこそ、しっかり後片付けをする習慣をつけなきゃいけないんですね。あ、あと依存配列についても少し整理したいです。空の配列 [] を渡すと、どういう動きになるんでしたっけ?」
先生
「良い質問ですね。空の配列を渡すと、『このコンポーネントが最初に表示された時だけ実行する』という意味になります。最初のサンプルコードでタイマーをセットする時に使いましたね。逆に、依存配列に特定の変数を入れると、その変数が更新されるたびに useEffect が再実行されます。」
生徒
「使い分けが大事なんですね。応用例のストップウォッチでは、isActive を配列に入れていたから、ボタンを押すたびにタイマーのON/OFFが切り替えられたんだ!」
先生
「正解です!飲み込みが早いですね。Reactのフックは組み合わせ次第で無限の可能性があります。タイマーをマスターしたら、次はAPIでデータを取得して一定時間ごとに更新する『ポーリング処理』などにも挑戦してみると面白いですよ。」
生徒
「ポーリング!難しそうだけど、ニュースアプリとかで使われてそうですね。次はそれを調べてみます!Reactがますます楽しくなってきました。」
先生
「その意気です。エラーが出ても、それは成長のチャンス。一歩ずつ、楽しみながら学んでいきましょうね。」