5-3. useEffectでタイマーを作る

setIntervalとクリーンアップ関数を学びます

React - 第3章: React Hooks - useEffect

課題:useEffectでタイマーを作ろう

useEffectでは、クリーンアップ関数を使って、処理を止めることができます。 1秒ごとにカウントアップするタイマーを作って、停止できるようにしてみましょう。

やること

  1. 1.タイマーの状態を作る(初期値: 0)
  2. 2.実行中かどうかの状態を作る(初期値: true)
  3. 3.useEffectで1秒ごとにカウントアップする
  4. 4.「停止」ボタンでタイマーを止める
  5. 5.クリーンアップ関数でタイマーをクリアする

タイマーとクリーンアップ

setIntervalでタイマーを設定します:

useEffect(() => {
  if (isRunning) {
    const timerId = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);  // 1000ミリ秒 = 1秒
    
    // クリーンアップ関数
    return () => {
      clearInterval(timerId);
    };
  }
}, [isRunning]);

クリーンアップ関数の役割:

  • useEffectから関数を返すと、それがクリーンアップ関数になる
  • 次のuseEffectが実行される前、またはコンポーネントが消える前に実行される
  • タイマーやイベントリスナーなど、止めるべき処理を書く

ポイント:setIntervalを使ったら、必ずclearIntervalでクリアします。これを忘れるとタイマーが止まりません。

期待される出力

経過時間: 5秒
[停止]
import { useState, useEffect } from 'react';

function App() {
  const [seconds, setSeconds] = useState(0);
  const [isRunning, setIsRunning] = useState(true);
  
  // ここにuseEffectを追加してください
  // isRunningがtrueの時だけタイマーを動かす
  // 1秒ごとにsecondsを+1する
  // クリーンアップ関数でタイマーをクリアする
  
  
  return (
    <div style={{ padding: '20px', textAlign: 'center', maxWidth: '400px', margin: '0 auto' }}>
      <h2>タイマー</h2>
      
      <p style={{ 
        fontSize: '48px', 
        fontWeight: 'bold',
        marginTop: '20px'
      }}>
        {seconds}</p>
      
      <p style={{ fontSize: '18px', color: '#666', marginTop: '10px' }}>
        経過時間
      </p>
      
      {isRunning ? (
        <button 
          onClick={() => setIsRunning(false)}
          style={{ 
            marginTop: '30px',
            padding: '10px 30px',
            fontSize: '16px',
            backgroundColor: '#dc3545',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer'
          }}
        >
          停止
        </button>
      ) : (
        <button 
          onClick={() => setIsRunning(true)}
          style={{ 
            marginTop: '30px',
            padding: '10px 30px',
            fontSize: '16px',
            backgroundColor: '#28a745',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer'
          }}
        >
          再開
        </button>
      )}
    </div>
  );
}

export default App;