【ポモドーロタイマーを作ろうVanillaJS編④】~リファクタリングとボタンクリック制御~

今回は、前回実装した自動サイクル部分のリファクタリング
START、STOP、RESETボタンをクリックした際の動きを実装していきます。


前回の記事はこちら
naomi-homma.hatenablog.com


自動サイクル部分のリファクタリング

前回のコードでは各ターン毎に関数を作成していたので

同じような処理を書いている部分が多々ありました。

このままでは、コード量が無駄に多いですし、メンテナンス性も良くないので
重複している部分は可能な限りまとめて書くように意識しました。


考え方は以下です。


①カウントダウン関数と描画処理は全てのターンで共通なのでまとめる


②①を行った場合、自動的にサイクルを回すには
 残り時間がゼロになったタイミングで
 次にどのような処理を行うかを判断する必要がある


③②を実装するために、stateとcycleCountの値で条件分岐させる


この考えに基づいて書いたコードがこちらです。

//初期値
const workTime = 25; //作業時間
const breakTime = 5; //小休憩時間
const longBreakTime = 20; //長休憩時間

// StateとCountを表示
function displayState (state, count) {
    timeState.textContent = state;
    if (state === "work")
      cycle.textContent = `${count}/${Number(cycleCount)+1}`;
}

// timerを表示
function displayTime(time) {
    timer.textContent = `${String(time).padStart(2, '0')}:00`;
}

function countDown(time) {
    const remainigTime = (time * 60 *1000) - elapsedTime - (Date.now() - startTime);
    const restMin = String(Math.floor((remainigTime / 1000 / 60) % 60)).padStart(2, '0');
    const restSec = String(Math.floor((remainigTime / 1000) % 60)).padStart(2, '0');
    timer.textContent = `${restMin}:${restSec}`;
    if (remainigTime >= 0) {
      timeoutId = setTimeout(() => {
        countDown(time);
        }, 100); 
    } else if (remainigTime < 0 ) {
      clearTimeout(timeoutId);
      startTime = Date.now();
      elapsedTime = 0;
      if (count === cycleCount+1 && state === "work") {
        state = "longBreak";
        displayState(state);
        countDown(longBreakTime);
      } else if (state === "work") {
        state = "break";
        displayState(state);
        countDown(breakTime);
      } else if (state === "break") {
        state = "work";
        count ++;
        displayState(state, count);
        countDown(workTime);
      } else if (state === "longBreak") {
        state = "work";
        count = 1;
        displayState(state, count);
        countDown(workTime);
      }
    }
  }


countDown関数としてまとめました。

stateとcycleCountの値によって条件分岐させ

次に呼び出したいターンの時間を引数として渡して

再度、自分自身(countDown関数)を呼び出しています。


初学者だとこの「引数」の理解に躓くことが多いようで
(少なくとも私は少し前まではあやふやでした)

この引数を渡すという方法がなかなか思いつかないかもしれません。


私が引数で躓いたポイントは、引数の名称が渡す側と受け取る側で異なる
(同じ時もありますが)という部分でした。


数学を学んでいた時のなごりなのか、値が代入される箱は
同じ名称になる....はず.....と思い込んでいたので、
引数の受け渡しをすんなり理解することができませんでした。


今回の実装を通して以前よりは理解できたように感じます。
引数便利!


まとめられる部分はさらに関数化


さらに、同じ処理を書いている部分をまとめました。

stateとcount、timerの表示部分です。

前者では引数を二つ渡すことを覚えました(初歩...)


  // StateとCountを表示
function displayState (state, count) {
    timeState.textContent = state;
    if (state === "work")
      cycle.textContent = `${count}/${cycleCount}`;
}

// timerを表示
function displayTime(time) {
    timer.textContent = `${String(time).padStart(2, '0')}:00`;
}

ボタンのクリック可否を制御する


続いて、START、STOP、RESETボタンのクリックイベントの設定です。

ここでは可読性を上げるためにswitch文で条件分岐させています。

処理をまとめたのですっきり書けるようになりました。


start.addEventListener('click', () => {
    startTime = Date.now();
    switch (state) {
      case "work": countDown(workTime);
        break;
      case "break": countDown(breakTime);
        break;
      case "longBreak": countDown(longBreakTime);
        break;
    }
  });

  stop.addEventListener('click', () => {
    clearTimeout(timeoutId);
    elapsedTime += Date.now() - startTime;
  });

  reset.addEventListener('click', () => {
    switch (state) {
      case "work": displayTime(workTime);
        break;
      case "break": displayTime(breakTime);
        break;
      case "longBreak": displayTime(longBreakTime);
        break;
    }
    elapsedTime = 0;
  });


今回は以上になります。


次回は、各ターンが終了した際の効果音の導入と
userが各時間を設定できるよう、テキストフィールドの作成を行う予定です。




↓↓私の師匠、もりけんさんの武骨日記。問題集、要チェック

kenjimorita.jp