こんにちは!Reactの学習、楽しく進めていますか?
画面がサクサク動いて、自分の思い通りに表示が変わっていくのを見ると、「おっ、なんかすごいぞ!」ってワクワクしますよね。
でも、Reactを触り始めて少し経つと、必ずと言っていいほどぶつかる「大きな壁」があるんです。そう、それが今回お話しする`useEffect`とタイマー処理(`setTimeout`や`setInterval`)の組み合わせです。
「画面を開いた後にカウントダウンを始めたいだけなのに、なぜかタイマーが何重にも動いてバグる……」
「ページを移動したのに、裏でずっとタイマーが動き続けてエラーが出る……」
そんな経験、ありませんか?
大丈夫ですよ、安心してください。最初は誰もが「なんだこれ呪文か?」って頭を抱えます。私も昔は盛大にやらかしていました。
今回は、身近な例えを交えながら、このタイマーの制御方法を一緒にスッキリと紐解いていきましょう!
—
1. タイマー処理は「キッチンタイマー」だと思っておこう
まず、`setTimeout`(指定時間後に1回だけ実行)や、`setInterval`(指定時間ごとに繰り返し実行)を扱うときの心構えから。
これらは、いわば「キッチンタイマー」です。
料理をするときに「3分後にパスタ茹で上がりのアラームを鳴らそう」とタイマーをセットしますよね。あのタイマー、もし料理が終わってキッチンを離れるとき、どうしますか?
そのまま放置して外出したりしませんよね。うるさいし、電気代(いや、電池か!)ももったいない。ちゃんと「ピピッ」と止めて、棚に片付けますよね。
Reactのコンポーネント(画面の部品)もこれとまったく同じなんです。
画面に出てきたときはタイマーを「カチッとセット」する。でも、その画面から別のページに移動したり、部品が消えたりするときは、必ずタイマーを「オフにする(クリアする)」というお片付けが必要なんです。
このお片付けをサボると、Reactの裏側で「もう誰もいない部屋に向かって、ずっとアラームを鳴らし続ける幽霊タイマー」が暴れ回ることになります。これが、バグの正体です。
—
2. `useEffect`ってなぁに?(おまじないの正体)
Reactで「画面が表示されたタイミングで何かしたい!」というときに使うのが、`useEffect`というフック(機能)です。
書き方の基本はこんな形をしています。
useEffect(() => {
// ここに「やりたいこと(タイマーのセットなど)」を書く
return () => {
// ここに「お片付け(クリーンアップ関数)」を書く
};
}, [/ 依存配列 /]);
この「returnの中身」がめちゃくちゃ重要です。こここそが、先ほどお話しした「キッチンタイマーを止めるボタン」なんですよ。
- コンポーネントが出てきたとき:`useEffect`の中のメイン処理が動く(タイマーON!)
- コンポーネントが消えるとき(または次に切り替わる前):`return`の中身が動く(タイマーOFF!)
この「セットと片付けのワンセット」を覚えるだけで、Reactの副作用(画面描画以外の処理)の怖さはグッと減ります。
—
3. 【実践】1秒ごとに数字が増えるカウンターを作ってみよう
百聞は一見に如かず。実際に、1秒ごとにカウントが増えていく時計のようなコンポーネントを作ってみましょう。
お使いのエディタにそのまま貼り付けて動かせるコードを用意しました。コメントを読んで、流れを感じてみてくださいね。
import React, { useState, useEffect } from ‘react’;
export default function TimerComponent() {
// 秒数を管理する「count」という状態(State)を作ります。初期値は0です。
const [count, setCount] = useState(0);
useEffect(() => {
// 【1. タイマーのセット(お仕事開始)】
// 1000ミリ秒(=1秒)ごとに、countの数字を「今より1増やす」処理を予約します。
const timerId = setInterval(() => {
// 古い状態を安全に更新するために、コールバック関数形式(prev => …)を使っています
setCount((prevCount) => prevCount + 1);
}, 1000);
// デバッグ用にコンソールを出してみます
console.log(‘タイマーをセットしました! ID:’, timerId);
// 【2. お片付け(クリーンアップ関数)】
// このコンポーネントが画面から消えるとき、または再レンダリングされる直前に実行されます。
return () => {
clearInterval(timerId); // 動かしていたタイマーをしっかり止めます!
console.log(‘タイマーをキレイに片付けました!’);
};
}, []); // 依存配列が空([])なので、「画面に最初に現れた時」にだけタイマーをセットします。
return (
⏱️ 優しいタイマー実験室
経過時間: {count} 秒
(ブラウザのコンソールを開いて、動きを確認してみてくださいね)
);
}
このコードの優しいポイント
1. `setInterval`で返ってくる「タイマーのID(管理番号のようなもの)」を、`timerId`という変数にしっかり捕まえています。
2. `return () => { clearInterval(timerId); }` の部分で、そのIDを指定して「このタイマーを止めて!」と指示を出しています。これがタイマー制御のゴールデンルールです。
—
4. よくあるつまずきポイント:「あれ?タイマーが2倍速で動く!?」
初心者の頃、私もやりましたし、質問サイトでも本当によく見かける現象があります。
それは、依存配列(`useEffect`の末尾にある `[]` の部分)の書き方を間違えて、タイマーが何個も同時に動き出してしまう現象です。
例えば、もし依存配列に `[count]` なんて書いちゃったらどうなるでしょう?
// ⚠️ 悪い例(やってしまいがちなミス)
useEffect(() => {
const timerId = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(timerId);
}, [count]); // ← ここに count を入れちゃった!
これだと、「`count`が変わるたびに、`useEffect`が一度お片付けをして、新しいタイマーをセットし直す」という動きになります。
結果として、1秒ごとにタイマーが生まれ変わり、処理が重なったり、予期せぬ挙動を引き起こしたりします。「なんか数字の進み方がおかしいぞ……?」となったら、大体この依存配列の指定ミスが原因です。
基本のルール:
タイマーの中で現在の状態(State)を安全に更新したいときは、さきほどのサンプルコードのように 関数型アップデート(`setCount(prev => prev + 1)`) を使って、依存配列をスッキリ空(`[]`)にしてあげるのが、最も安全でトラブルの少ない王道テクニックです。
—
5. まとめ
いかがでしたでしょうか?
`useEffect`でのタイマー制御、難しく感じられたかもしれませんが、要点を整理するととてもシンプルです。
- タイマーを使ったら、必ずセット(`setInterval` / `setTimeout`)と片付け(`clearInterval` / `clearTimeout`)をセットで書く。
- お片付けは、`useEffect`の中の `return () => { … }` に書く。
- コンポーネントが消えるときや切り替わるときに、裏で暴れないようにするためのお作法。
最初はコードのお作法が多くて戸惑うことも多いですが、一つひとつ意味を理解していくと、Reactは本当に素直で頼もしい相棒になってくれます。
あなたのフロントエンド開発の旅が、少しでも楽しく、スムーズなものになりますように。
つまずいたときは、いつでもまたこの記事に帰ってきてくださいね。応援しています!

コメント