Next.js と TypeScript で、タイマーアプリを作る

TwitterFacebookHatena

TL;DR

このページでは、「タイマーアプリを作る」方法について解説します。一言で言うと「タイマーアプリ」とは、特定の時間を設定し、その時間が経過したら通知するアプリのことです。

開発環境 バージョン
Next.js 13.4.4
TypeScript 5.0.4
Emotion 11.11.0
React 18.2.0

「タイマーアプリ」コンポーネント

「タイマーアプリ」は、指定した時間が経過したら通知を出すというシンプルなアプリケーションです。ただし、そのシンプルさがユーザーにとって使いやすさを生むため、実装面では細部まで注意を払う必要があります。最も基本的な要素としては「時間の設定」「時間のカウントダウン」「時間経過の通知」の 3 つが挙げられます。

以下に、一番基本的なタイマーアプリの機能をもつコンポーネントを見ていきましょう。

// ファイル名: components/BasicTimer.tsx
// ReactとReactのHooksであるuseStateをインポートしています
import React, { useState } from 'react'

// プロパティの型定義を行います。ここでは、初期カウントをnumber型で受け取るようにしています
type Props = {
  initialCount: number
}

// Functional Componentを定義します。Props型の初期カウントを受け取ります
const BasicTimer = ({ initialCount }: Props) => {
  // カウントの状態とその更新関数を定義します。初期値はPropsから受け取った初期カウントです
  const [count, setCount] = useState(initialCount)

  // tick関数を定義します。カウントが0より大きいとき、カウントを1減らします
  const tick = () => {
    if (count > 0) setCount((prevCount) => prevCount - 1)
  }

  // useEffect Hookを使って、コンポーネントのレンダリング後に実行する処理を設定します
  React.useEffect(() => {
    // 1秒ごとにtick関数を実行するタイマーを設定します
    const timerId = setInterval(() => {
      tick()
    }, 1000)

    // クリーンアップ関数を定義します。この関数はコンポーネントがアンマウントされたとき、
    // または依存配列が更新されたときに呼ばれます。
    // ここではタイマーをクリアします
    return () => clearInterval(timerId)
  }, [count]) // 依存配列にカウントを指定します。
  // これにより、カウントが変更されるたびにエフェクトが実行されます

  // カウントを表示するdiv要素をレンダリングします
  return <div>{count}</div>
}

// BasicTimerコンポーネントをエクスポートします。
// 他のファイルからimportして使うことができます
export default BasicTimer

このコンポーネントでは、React のuseStateuseEffectを使って、カウントダウンの機能を実装しています。useStateは状態管理のためのもので、ここではタイマーのカウントを管理しています。useEffectは副作用を扱うためのもので、ここでは時間が経過するたびにカウントを減らしています。

このコードをより詳しく見ていきましょう。

まず、useStateを使ってカウントの初期値を設定します。次にtickという関数を定義し、カウントが 0 より大きい場合はカウントを 1 減らすようにします。そして、useEffectを使って 1 秒ごとにtick関数を実行します。これにより、カウントダウンの機能が実現できます。また、useEffectのクリーンアップ関数でタイマーをクリアして、メモリリークを防ぎます。

以上が、タイマーアプリの基本的な仕組みとなります。しかし、現実のタイマーアプリではさらに多くの機能が必要となるでしょう。例えば、時間の設定、カウントダウンの一時停止、再開、リセットなどの機能が求められます。次のセクションでは、これらの機能を含むより実用的なタイマーアプリを Next.js と TypeScript で作成していきます。

Next.js で、タイマーアプリを実装

さて、次にタイマーアプリの実装に移ります。ここでは、基本的なタイマー機能に加えて、「時間の設定」「カウントダウンの一時停止」「再開」「リセット」の各機能を実装していきます。

まずは、タイマーコンポーネントの全体のコードから見ていきましょう。

// ファイル名: components/TimerApp.tsx
// ReactとReactのHooksであるuseStateをインポートします
import React, { useState } from 'react'

// プロパティの型定義を行います。初期カウントをnumber型で受け取ることができます
type Props = {
  initialCount: number
}

// TimerAppというFunctional Componentを定義します。Props型のinitialCountを受け取ります
const TimerApp = ({ initialCount }: Props) => {
  // カウントとその更新関数をuseState Hookで定義します。
  // 初期値はPropsから受け取った初期カウントです
  const [count, setCount] = useState(initialCount)
  // タイマーが動いているかどうかの状態とその更新関数をuseState Hookで定義します。
  // 初期値はfalseです
  const [isRunning, setIsRunning] = useState(false)

  // タイマーを開始する関数を定義します。isRunningの状態をtrueに更新します
  const start = () => setIsRunning(true)
  // タイマーを一時停止する関数を定義します。isRunningの状態をfalseに更新します
  const pause = () => setIsRunning(false)
  // タイマーをリセットする関数を定義します。カウントを初期カウントに戻し、
  // isRunningの状態をfalseに更新します
  const reset = () => {
    setCount(initialCount)
    setIsRunning(false)
  }

  // カウントダウンを実行する関数を定義します。
  // カウントが0より大きいとき、カウントを1減らします
  const tick = () => {
    if (count > 0) setCount((prevCount) => prevCount - 1)
  }

  // useEffect Hookを使って、コンポーネントのレンダリング後に実行する処理を設定します
  React.useEffect(() => {
    let timerId: NodeJS.Timeout | null = null

    // タイマーが動いていて、カウントが0より大きい場合、
    // 1秒ごとにtick関数を実行するタイマーを設定します
    if (isRunning && count > 0) {
      timerId = setInterval(() => {
        tick()
      }, 1000)
    }

    // クリーンアップ関数を定義します。
    // この関数はコンポーネントがアンマウントされたとき、
    // または依存配列が更新されたときに呼ばれます。ここではタイマーをクリアします
    return () => {
      if (timerId) clearInterval(timerId)
    }
  }, [isRunning, count])
  // 依存配列にisRunningとカウントを指定します。
  // これにより、いずれかが変更されるたびにエフェクトが実行されます

  // カウントと操作ボタンを表示するdiv要素をレンダリングします
  return (
    <div>
      <div>{count}</div> {/* 現在のカウントを表示します */}
      <button onClick={start}>Start</button> {/* Startボタン。クリックするとstart関数が実行されます */}
      <button onClick={pause}>Pause</button> {/* Pauseボタン。クリックするとpause関数が実行されます */}
      <button onClick={reset}>Reset</button> {/* Resetボタン。クリックするとreset関数が実行されます */}
    </div>
  )
}

// TimerAppコンポーネントをエクスポートします。他のファイルからimportして使うことができます
export default TimerApp

タイマーの制御

このコードでは、まずuseStateを使ってcountisRunningの 2 つの状態を管理しています。

次に、タイマーの一時停止、再開、リセットを行うためのstartpausereset関数を定義しています。start関数はタイマーを開始するために、isRunningtrueに設定します。pause関数はタイマーを一時停止するために、isRunningfalseに設定します。そして、reset関数はカウントを初期値に戻し、タイマーを停止するために、isRunningfalseに設定します。

カウントダウン処理

カウントダウンの処理は、tick関数とuseEffectフックを使って行います。tick関数はカウントが 0 より大きい場合にカウントを 1 減らします。そして、useEffectフックを使って、タイマーが動作していてカウントが 0 より大きい場合に、1 秒ごとにtick関数を実行します。

コンポーネントのレンダリング

最後に、タイマーの値と、タイマーの開始、一時停止、リセットを行うボタンをレンダリングします。ボタンはそれぞれonClickプロパティを使って、対応する関数を呼び出します。

以上がタイマーアプリの実装となります。ここまでで、タイマーアプリの基本的な機能を持つコンポーネントが完成しました。次にこのコンポーネントを呼び出す方法を見ていきましょう。

タイマーアプリの呼び出し方法

次に、作成したTimerAppコンポーネントを呼び出す方法を見ていきます。TimerAppコンポーネントはinitialCountというプロパティを受け取ります。このプロパティはタイマーの初期値として使われます。

// ファイル名: pages/index.tsx
import React from 'react'
import TimerApp from '../components/TimerApp'

const IndexPage = () => {
  return (
    <div>
      <h1>Timer App</h1>
      <TimerApp initialCount={60} />
    </div>
  )
}

export default IndexPage

以上が、タイマーアプリの呼び出し方になります。ここでは、TimerAppコンポーネントに60という初期値を与え、1 分のタイマーを作成しています。

以上で、Next.js と TypeScript を使ったタイマーアプリの作成は完了です。これらのコードを活用して、自分だけのタイマーアプリを作成してみてください。

Next.js と TypeScript で、タイマーアプリを作る