vue3でのカウントダウンタイマーの実装

クイズアプリ実装時の時間設定

vue3 Composition APIでのカウントダウンタイマーの実装を行ったので
自分が再度実装するときの記事として残しておきます。
javascriptなどでは情報があったのですがvueでは探しても見つからなかったので。

実装条件

  1. カウントダウンは6分。
  2. 分、秒でカウントをしていき、5分00秒→4分59秒のようになる。

まずは全体コードを記載します。

script
import { ref, computed, onMounted } from 'vue'

const minutes = ref(6)
const seconds = ref(0)

let intervalId = null

const startTimer = () => {
  intervalId = setInterval(() => {
    if (minutes.value === 0 && seconds.value === 0) {
      clearInterval(intervalId)
      return
    }
    if (seconds.value === 0) {
      minutes.value--
      seconds.value = 59
    } else {
      seconds.value--
    }
  }, 1000)
}

const formattedTime = computed(() => {
  const formattedMinutes = minutes.value.toString().padStart(2, '0')
  const formattedSeconds = seconds.value.toString().padStart(2, '0')
  return `${formattedMinutes}:${formattedSeconds}`
})

onMounted(() => {
  startTimer()
})
template
<div>
      <div>残り時間</div>
      <div>{{ formattedTime }}</div>
</div>

文脈ごとに解説していきます。

const minutes = ref(6)
const seconds = ref(0)

minutesとsecondsと2つのリアクティブ変数を定義。ref(6)は初期値を6にしてタイマーを6分にします。

let intervalId = null

intervalIdという変数を定義して初期値をnullに。
後にsetIntervalId()の戻り値を割り当て、タイマーの戻り値を停止する。

const startTimer = () => {
  intervalId = setInterval(() => {
    if (minutes.value === 0 && seconds.value === 0) {
      clearInterval(intervalId)
      return
    }
    if (seconds.value === 0) {
      minutes.value--
      seconds.value = 59
    } else {
      seconds.value--
    }
  }, 1000)
}

カウントダウンの制御部分です。
startTimer関数を定義します。
intervalId = setInterval(() =>、setInterval()は指定された時間間隔ごとに指定された関数を実行するjavascript関数
アロー関数を使い中にタイマーロジックを実装。

if (minutes.value === 0 && seconds.value === 0)は
if文を使いminutes分が0かつsecondsが0になったとき、つまりタイマーが0分0秒になったとき、
clearInterval(intervalId)を使用してタイマーを停止します。
intervalIdをクリア。

if (seconds.value === 0) {
minutes.value--
seconds.value = 59
} else {
seconds.value--
}
はseconds(秒)が0かどうかを判断しています。0なら処理を実行。
minutes.valueを1減らす(5分00秒→4分59秒になる)。
seconds.value = 59は59にリセット(59秒になる)。
secondsが0でない場合はsecondsを1減らす(秒が1秒減る)。


}, 1000)
で1000ミリ秒(1秒)ごとに関数が実行される。

const formattedTime = computed(() => {
  const formattedMinutes = minutes.value.toString().padStart(2, '0')
  const formattedSeconds = seconds.value.toString().padStart(2, '0')
  return `${formattedMinutes}:${formattedSeconds}`
})

時間を分と秒で分けて表示します。
vue.js本体で用意されているcomputed()関数を利用してformattedTime計算プロパティを定義
ここではcomputed()関数は割愛しますが簡単に説明すると
ここでは計算結果をリアルタイムで表示してくれます。
computed()関数を使うためにimport { ref, computed } from 'vue'にします。

const formattedMinutes = minutes.value.toString().padStart(2, '0')
ではminutes(分)というリアクティブ変数が変更されるたびに自動計算される。
padStart(2, '0')は分を2桁の数字に変えています。
例えばコードを書かなければweb上の表示は05:59→5:59になる。
secondsも同様の処理を行います。
ruturnとして`${formattedMinutes}:${formattedSeconds}`web上の表示を2桁整数の分:秒で表示します。

onMounted(() => {
  startTimer()
})

コンポーネントがマウントされた後にstartTimerが実行。
忘れずにimport { ref, computed, onMounted } from 'vue'にして
onMountedをインポートしておきます。

以上で実装完了です。
そもそもvueでカウントダウンを実装する必要があったのかは疑問ですが、
今回私がオリジナルアプリケーションを作成する中で
時間制限を設けたかったので実装してみました。
私が使用した参考書も載せておきます。

過去1長く、悩んだ箇所ですが形になったのでよかったです。

vue.jsでのクイズアプリ作成の課題点

Vue.jsでの現状の課題点を浮き彫りに。

現在作成中のクイズアプリケーションの課題点、変更点を整理しようと思う。

  1. テスト開始から10分間のタイマーを起動。
  2. 4択問題を選択していき、テストを終了するボタンを押したらローカルストレージに保存してページを移動する。
  3. ローカルストレージに保存とページを移動するのは一つのボタンで行う。
  4. ページ移動後は正解した数、間違えた問題のみ表示される。

以上が解決するべき課題になる。
現状は一問一問に@clickをつけてしまっているので選択するたびにローカルストレージに保存されてしまう問題を何とかするのが課題

バブルソートについて

バブルソートアルゴリズムRubyを使い学んでいく。

某プログラミングスクールのアルゴリズムのカリキュラムを、
修理しているノートPCが返ってくる前に終わらせられそうです。

バブルソートとは?
整列アルゴリズムの1つで配列の隣り合う
データの大小を比較して並べる。
バブルソートを使用した整列は末尾の比較と交換からスタート。
数字が正しく整列できていなければ交換を行い、
先頭の方向に向かいながら順に整列を行う。
先頭には比較をすべての要素と行った後の要素が来るので、
整列できていると確定する。
繰り返し処理、二重ループを使い実装していく。


[11,45,55,22,33]→[11,44,55,22,33]→[11,44,22,55,33]→
[11,22,44,55,33]→[11,22,44,33,55,]→[11,22,33,44,55]

バブルソートのポイントは
・二重ループ
・変数同士の値の交換方法
・交換回数を意識した二重ループの範囲指定方法

例題

def bubble_sort(ary)
  (1...ary.length).each do |i|
    (i...ary.length).reverse_each do |j|
      if ary[j -1] > ary[j]
        tmp = ary[j - 1]
        ary[j - 1] = ary[j]
        ary[j] = tmp
      end
    end
  end
  return ary
end

ary = [245,45,32,367,122,67,14,89]
p ary
p bubble_sort(ary)

解説
ary配列の定義

p bubble_sort(ary)

で関数の呼び出し

(1...ary.length).each do |i|

で繰り返し処理を実行する。
1...ary.lengthは配列の要素分、変数iは各要素のインデックスを取得。
このループは、配列の要素の数より1少ない回数繰り返されます。

(i...ary.length).reverse_each do |j|

reverse_eachで配列の後ろから処理を開始できるようになる。
変数jは現在比較交換をしようとしている要素を選択している。
つまり最初のループでは89を選択している。

if ary[j -1] > ary[j]

各ループ内で隣接する要素を比較します。
この場合左隣が大きければif構文が発生するようにします。

tmp = ary[j - 1]
ary[j - 1] = ary[j]
ary[j] = tmp

if隣接する要素の方が大きい場合if処理が発生し、
要素の交換を行います。
交換する場合一時的な変数 tmp を使用して、
要素を交換します。
具体的には、ary[j - 1] の値を tmp に保存し、
ary[j - 1] に ary[j] の値を代入し、
最後に ary[j] に tmp の値を代入します。

最後にreturn aryで整列された配列を返却処理しましょう。
これで配列aryの要素を並び替える実装ができました。

(i...ary.length).reverse_each do |j|の変数iにする訳は?

さて内側のループはなぜ(1...ary.length)ではないのか
1にすると確定した要素も比較対象に入ってしまうからです。
1週目のループで要素14が一番左に行きます。
しかし2回目のループを行う際確定している14を
比較するのは無駄になってしまいます。
変数iが1の時、.reverse_eachで行うソートで最小値14が確定します。
2週目の際には変数iは2になります。
最小値14は1にあるので14は除外されます。

ここからはさらに詳しく、1回目の内側のループの処理について深堀。

ary = [14,245,45,32,367,122,67,89]
(1...ary.length).each do |i|
  (i...ary.length).reverse_each do |j|
   if ary[j -1] > ary[j]
      tmp = ary[j - 1]
      ary[j - 1] = ary[j]
      ary[j] = tmp
    end
  end

で末尾から繰り返し比較していく、まず89と14を比較してifは起こらない
次に末尾から2つ目14と67を比較して初めてifが起こり、要素の交換が起こります。
配列の中身は[245,45,32,367,122,14,67,89]になる。
次に末尾から3つ目14と122を比較してifが起こり、要素の交換が起こる。
配列の中身は[245,45,32,367,14,122,,67,89]になる。
次に末尾から4つ目14と347を比較してifが起こり、要素の交換が起こる。
配列の中身は[245,45,32,14,367,122,,67,89]になる。
次に末尾から5つ目14と32を比較してifが起こり、要素の交換が起こる。
配列の中身は[245,45,14,32,367,122,,67,89]になる。
次に末尾から6つ目14と45を比較してifが起こり、要素の交換が起こる。
配列の中身は[245,14,45,32,367,122,,67,89]になる。
次に末尾から7つ目14と245を比較してifが起こり、要素の交換が起こる。
配列の中身は[14,245,45,32,367,122,,67,89]になる。
これで内側のループが終わり1回目の外側のループが終わる。

では外側の2回目のループを深堀していく、

もう理解しているという人は見る必要はないですね。

ary = [14,245,45,32,367,122,67,89]
(1...ary.length).each do |i|
  (i...ary.length).reverse_each do |j|
   if ary[j -1] > ary[j]
      tmp = ary[j - 1]
      ary[j - 1] = ary[j]
      ary[j] = tmp
    end
  end

外側のループは2回目ですね.
内側のループは今の配列に注目して下さい。
(i...ary.length)は2回目のループなので実質(2...ary.length)
になり配列の先端14はソートから除外されます。
まず89と67を比較してifは起こらないし配列の変化なし
次に末尾から2つ目67と122を比較してifが起こり、要素の交換が起こる。
配列の中身は [14,245,45,32,367,67,122,89]になる。
次に末尾から3つ目67と367を比較してifが起こり、要素の交換が起こる。
配列の中身は [14,245,45,32,67,367,122,89]になる。
次に末尾から4つ目67と32を比較してifは起こらないし配列の変化なし。
配列の中身は [14,245,45,32,67,367,122,89]になる。
次に末尾から5つ目32と45を比較してifが起こり、要素の交換が起こる。
配列の中身は [14,245,32,45,67,367,122,89]になる。
次に末尾から6つ目32と245を比較してifが起こり、要素の交換が起こる。
配列の中身は [14,32,245,45,67,367,122,89]になる。

2回目のループなので先頭の14は比較から除外されています。
重要なのは67の比較と32の比較でifが起こっていない点です。
交換が行われなくても繰り返し処理は続くので
比較する数字が変わり67→32になります。

後は同じことの繰り返しを行うだけです。
アルゴリズムの勉強は難しいと思います。
例題を解くだけなら簡単ですが、
こうして記事に書いていく、自分なりの言葉で
解説していくほうが何倍も時間がかかります。
アルゴリズムの勉強は成果が出るわけでもないですが、
実力のある人はこういった勉強もしているのでしょうか。
あとこの記事の内容が間違っていたら申し訳ありません。

アルゴリズムとは?

Rubyによるアルゴリズムの学び

問題を解決するまでの1連の手順のこと
アルゴリズムの勉強をする意味は「論理的思考を養う」ことにある。


基本のアルゴリズム

アルゴリズム名	用途	     特徴
線型探索	探索	  容易に探索できる
二分探索	探索     高速に探索できる
選択ソート	ソート	容易にソートできる
バブルソート	ソート	容易にソートできる
クイックソート	ソート	拘束にソートできる。

アルゴリズムの基本構造3パターン
アルゴリズムの基本知識

基本知識	                            プログラミング用語
順番に処理を実行する 	                        順次実行
特定の条件を満たすまで繰り返し実行する	        条件分岐
特定の条件によって実行する処理を振り分ける	反復処理

・順次実行
プログラミングが書かれた順番に上から下へ処理を実行

puts = "処理1"
puts = "処理2"

・条件分岐
特定の条件が満たされた場合に実行する処理と満たされなかったときに実行する処理を分岐させる。

height = 140
if height > 150
  puts "OK"
else
  puts "NG"
end

・反復処理
特定の条件が成立する限り処理を繰り返し実行する処理

num = 1
while num <= 10
 puts num
 num += 1
end

アルゴリズムは基本的にこの3つの動作で表現する、この動作をアルゴリズムの制御構造という。
アルゴリズムを分解していくとこの3つの制御構造によってプログラムは構成されている。
例題などを丸暗記しても意味ないし、ソートをすべて100%学習する必要はないと思う。
ああ、こういった勉強したな、あれ?と思ったら自分で勉強したことを復習すればいいと思う。
ただ例題を知っているのと知らないのでは効率が違うし、わからなくなったらここに帰ってきたい。

探索アルゴリズムについて

*目的のデータを探すアルゴリズムを学ぶ

vue.jsの勉強を書籍使ってしていたところ、使っているノートPCのキーの反応がまったくなく修理に...
サンプルアプリは完成して落ち着いたので、苦手だったアルゴリズムの勉強を少しずつ進めていきたい。


探索アルゴリズムとは任意の数〇個のデータ(配列の)中から目的の値を探すアルゴリズム
rubyを使って学んでいきます。

今回は探索アルゴリズムの中から基本の線形探索と二分探索について書いていく。

*線形探索(リニアサーチ)

array = [14,32,45,67,89,122]
例えば89という目的の数字を探したいアルゴリズム
目的のデータがどこにあるか?配列の先頭14から122まで順番に探す。
最初に14と89を比較、次に32と89を比較し89が見つかるまで比較する。
実際に線形探索のコードを書いてみる。

例題

def  linear_search(array,target)
  array.each.with_index(1) do |num,index|
    if num == target
      puts "探している値は#{index}番目にあります"
      return
    end
  end
  puts "探している値はありませんでした。"
end
array = [24,30,45,67,90,12,454,100]
linear_search(array,45)
linear_search(array,5)

解説
array = [配列]を定義
linear_search(array,45)でdef linear_search(array,target)を呼び引数を入れる。
array.each.with_index(1)で配列内を順番に探索、indexは配列内の要素のインデックス
条件分岐を行いtarget例だと45が見つかったら出力


.each.with_indexについて
rubyのメソッドで通常のeachブロックから要素を取り出す際要素のインデックスは渡さないが
each.with_indexを使うことでインデックスを取得しながら要素を処理することができる。
例題だと(1)はインデックスの開始値を指定している。(0)にすると配列内の24のインデックスは0になる。
なのでeach.with_index(1)で24を出力すると探している値は1番目にありますとでる。

*二分探索(バイナリーサーチ)

探索対象のデータの探索範囲を半分にして目的のデータを探すアルゴリズム
探索対象の配列内のデータはあらかじめ小さい順にしておく必要がある。
二分探索は目的の値が中心地より大きいほうに存在するか小さいほうに存在するか判断しながら探索する。

array = [14,32,45,67,89,122,245,367]
目的の値89とする
目的の値が配列の中心より大きいか小さいかを判断する。67より大きい?小さい
目的の値の方が大きければ配列の中心値より大きい方を残す。67より89のがおおきいから
[89,122,245,367]を残す。
目的の値(89)は中心値より小さい方を残すので[89,122]が残る
あとは繰り返し目的の値を探す。

例題

def binary_search(array,target)
  head= 0
  tail = array.length - 1
  while head <= tail
    center = (head + tail) /2
    if array[center] == target
      puts "#{target}#{center + 1}番目にありました"
      return
    elsif array[center] < target
      head = center +1
    else
      tail = center - 1
    end
  end
  puts "#{target}は見つかりませんでした。"
end
array = [14,32,45,67,89,122,245,367]
puts"探索したい値を以下から選んで、入力してください。"
puts array.join(",")
target = gets.to_i
binary_search(array,target)

解説

head 配列の探索範囲の先頭の位置
tail   配列の探索範囲の末尾の位置
center 配列の中央の位置
target 探索する値
とします。

配列arrayを作成。
①joinで配列の中から探索したい数を入力。入力した後はtargetに代入。binary_search(array,target)で引数を渡す。
②メソッドの引数に探索対象である配列の先頭head=0と末尾の位置を示すtail=array.length - 1変数を定義
③while文で繰り返し処理の実装。探索範囲を表すheadとtailは繰り返し処理のたびに
 head=1,tail=8 head=2 tail=7と範囲が狭まるためhead <= tailの条件を満たす限り繰り返し処理が行われる。
④繰り返し処理内で中心値を定義するためcenter = (head + tail) /2を記述。
 center は初めて (0 + 7) / 2 で計算。array = [14, 32, 45, 67, 89, 122, 245, 367]だと67
⑤array[center] == targetで探している変数targetの値を配列の中心地であるarray[center]を比較結果に応じて条件の分岐を行 う
 targetと中心値が一致すると結果を出力。returnで繰り返し処理を抜ける。
⑥次にelsif array[center] < targetで入力したtargetが中心値より大きい場合の条件分岐を行う。
 探索範囲を狭めるためhead = center +1を記述。
 探索範囲の先頭を中央の要素より1つ右に移動する。head = 0、tail = 7、center = 3 の場合、中央の要素は配列の中で 67 。    
 そして、head =center + 1を実行すると、headは4になる
 探索範囲は[89,122,245,367]になる
⑦else  tail = center - 1でtargetが中心地array[center]より小さい場合の条件分岐
末尾tailをcenter -1, tail = center - 1 を実行すると、tail は 2 になり探索範囲は[14,32,45]

targetが32の場合の処理は
head= 0
tail = array.length - 1
center = (0 + 7) /2で3 [14,32,45,67,89,122,245,367]だと67で4
if array[cente] == target array[67] == 32ではない,elsif array[67] < 32でもないのでelseが実行
tail = center - 1は 7 = 4 - 1でtailは3に更新 配列内は[14,32,45,67]に
while文に戻り center = (head + tail) /2は center = (0 + 3) /2 になりcenter= 1に
array[center] == target はarray[1]== 1になり出力し処理を抜ける。

初期状態で head = 0、tail = 7 の配列 [14,32,45,67,89,122,245,367] が与えられる。
中央のインデックスを計算し、center = (0 + 7) / 2 = 3 となり、中央の要素は 67 になります。
中央の要素と target を比較し、一致しないため、67 は 32 よりも大きいことが分かります。
else ブロックが実行され、tail が center - 1 に更新され、tail = 3 となります。
while ループに戻り、新しい中央のインデックスが計算され、center = (0 + 3) / 2 = 1 となります。
中央の要素と target を比較し、一致するため、32 が見つかったことを示すメッセージが出力され、探索が終了します。

インプット3割→アウトプット7割を意識したいのでonenoteに書いてからブログに乗せています。
実際にはコードを書いているより自分なりの解釈と、解説を書いている方が何倍も長いです。
次回はアルゴリズムの基本構造パターンをまとめて記事にしたい。

Vue.js Todoの登録

Todoの登録を行っていく。

ここではTodoの登録までをやっていきます。
必要最低限の機能のみ実装していきます。

APP.vue
<template>
   <input type="text" class="todo_input">
   <button>追加<buttom>
</template>

ここでは前の記事でのvue.jsの導入はしている。
コンポーネント化は省略しています。
自分なりに解釈して忘事録として残していきます。
次回はv-modelの解釈をしていきます。

vue3.2のsetupについて

script setupの記述方法

vue3.2からsetupの書き方が変わりました。
具体的にはscript内にsetupを記述することにより以下の通りに。

今までのvue 3.0では

import { ref, onMounted } from 'vue';

export default {
  setup() {
    // データの定義
    const count = ref(0);

    // メソッドの定義
    const increment = () => {
      count.value++;
    };

    // ライフサイクルフックの利用
    onMounted(() => {
      console.log('コンポーネントがマウントされました');
    });

    // コンポジショナルな構文での記述
    return {
      count,
      increment
    };
  }
};

だったのが、vue3.2では

<script setup>  
  import { ref } from 'vue'

  const count = ref(0)

  const increment = () => {
    count.value++
}
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

と記述を直すことができるようになりexport default、setup, return
記述が不要になりました。