プログラミング

Vue.jsでストップウォッチ! setTimeout ???

今回は、ドットインストールのJavaScript講座で紹介されているストップウォッチをVue.jsを使って作って見ました!

 

 

 

作ったソースコードは1番下に載せておきますね!

 

 

 

ちょっと難しいなと思ったのは、setTimeout()メソッドですね。

 

 

 

絶対に初心者が躓くところだろうな〜と思いました(私もつまずきました。。。)。自分の勉強がてら、解説してみたいと思います。

 

 

 

この部分がsetTimeoutメソッドですね。

 

timeoutId = setTimeout(() => { 
  this.countUp(); 
}, 10);

 

 

 

MDNによると、構文は以下のように書かれています。

 

var timeoutID = scope.setTimeout(function[, delay, arg1, arg2, ...]);
var timeoutID = scope.setTimeout(function[, delay]);
var timeoutID = scope.setTimeout(code[, delay]);

 

 

 

ここで解説するために初めてちゃんとMDNを見て見た気がしますが、うん、なにがなんだかわかりません。

 

 

 

適当に探していたら、JavaScriptドキュメントの見方が紹介されていましたページを見つけたので、これから公式ドキュメントも読むぞ!ってかたは見てみてください。

 

https://qiita.com/youya66/items/b8f67f68ff46df650fe4

 

 

 

よくわからなかったのは[]のところだったんですが、省略可能という意味のようですね。

 

 

 

実際にsetTimeoutを使うときは、

 

 

 

setTimeout(関数,時間)

 

 

 

という使い方をします。

 

 

 

「時間」後に「関数」を実行するという動きですね。

 

 

 

これをふまえたうえでソースコードに戻ると、なんか、あれってなるところがありますよね。

 

timeoutId = setTimeout(() => { 
  this.countUp(); 
}, 10);

 

 

()=>{}

 

 

 

この部分、なんでって思いません?

 

 

 

function(){}

 

 

 

じゃないのって感じですよね。

 

 

 

これは、

 

 

 

「アロー関数」

 

 

 

と呼ばれる関数です。

 

 

 

アロー関数と言っても、ようは関数なので、functionの記述に書き換えても動きます。

(正確には、構文エラーではないという意味です。普通の関数にすると、意図した動きにはなりません。実行時にエラーになります。)

 

//アロー関数
this.timeoutId = setTimeout(()=>{
  this.countUp();
}, 1000);

//普通の関数
this.timeoutId = setTimeout(function(){
  this.countUp();
},1000);

 

 

 

じゃぁ、なんでアロー関数になってるのって思いますよね?

 

 

 

結構色々調べて見たんですけど、いまいちよくわかる回答を見つけることができませんでした。

 

 

 

「アロー関数 this 束縛」

 

 

 

なんかでググってもらえるといろんな情報が出てきます。

 

 

 

すごくシンプルに説明すると、アロー関数を使った場合は、thiis.xxxのxxxの部分が関数が宣言されたときにある値に定まるんですよね。これをthisの束縛って呼んでるみたいです。

 

 

 

逆に、通常のfunctionで記述するとthis.xxxのxxxが定まらないので、this.xxxを実行する場所によって結果が変わってきてバグの元になるようです。

 

 

 

と、ここまではわかったんですけど、じゃぁなんで今回の例では動かないのかというのがよくわかりません。

 

 

 

とりあえず私の結論は、アロー関数が使えるところではとりあえずアロー関数を使うということです。

 

 

 

アロー関数自体にもよくないところはあるようなので、また問題になったらその時に学べばいいかなと思ってます。

 

 

 

 

では、ちょっと戻って、ここではsetTimeoutの戻り値をtimeoutIdに入れてますよね。

 

timeoutId = setTimeout(() => { 
  this.countUp(); 
}, 10);

 

 

 

このtimeoutIdはタイマーを止める時に使います。

 

 

 

こんな感じで。

 

clearTimer(){
  clearTimeout(this.timeoutId);
}

 

 

 

ここは特に説明いらないですよね。

 

 

 

超シンプルな、タイマーをスタートさせて止めるだけのプログラムも作って見たので、参考にしてください!

 

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>settimeout</title>
</head>
<body>
  <div id=app>{{ timer }}
    <p></p>

  <button v-on:click='countStart(), countUp()'>count start</button>
  <button v-on:click='clearTimer()'>clear timer</button>
  </div>
  
  
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    var vm = new Vue({
      el:'#app',
      data(){
        return {
          timer:"00:00:000",
          startTime:'',
          timeoutId:'',
        }
      },

      methods:{
        countUp() {
          console.log('countup called');

          const d = new Date(Date.now() - this.startTime);
          const m = String(d.getMinutes()).padStart(2, '0');
          const s = String(d.getSeconds()).padStart(2, '0');
          const ms = String(d.getMilliseconds()).padStart(3, '0');

          this.timer = `${m}:${s}:${ms}`;
          //ok
          this.timeoutId = setTimeout(()=>{
            this.countUp();
          }, 1000);

          //ng
          //this.timeoutId = setTimeout(function(){
          //  console.log("before CountUp")
          //  this.countUp();
          //  console.log("after CountUp")
          //},1000);

          //ok
          // this.timeoutId = setTimeout(function(){
          //   this.countUp();
          // }.bind(this), 1000);

        },
        countStart() {
          this.startTime = new Date();
        },
        clearTimer(){
          clearTimeout(this.timeoutId);
        }
      },

    })
  </script>
</body>
</html>

 

 

 

 

こちらは、ストップウォッチのソースコードです!

 

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Stopwatch Vue</title>

  <style>
    body {
      font-family: 'Courier New', Courier, monospace;
      font-size: 14px;
      background: #eee;
    }

    .container {
      margin: 20px auto;
      width: 270px;
      background: #fff;
      padding: 15px;
      text-align: center;
    }

    #timer {
      background: #ddd;
      height: 120px;
      line-height: 120px;
      font-size: 40px;
      margin-bottom: 15px;
    }

    .btn {
      width: 80px;
      height: 45px;
      line-height: 45px;
      background: #ddd;
      font-weight: bold;
      cursor: pointer;
    }

    .controls {
      display: flex;
      justify-content: space-between;
      user-select: none;
    }

    .inactive {
      opacity: 0.6;
    }
  </style>
</head>

<body>

  <div class="container">
    <div id="main">
      <div id="timer">{{ time }}</div>
      <div class="controls">
        <div class="btn" id="start" v-on:click="start()" v-bind:class="{inactive: isStart}">Start</div>
        <div class="btn" id="stop" v-on:click="stop()" v-bind:class="{inactive: isStop}">Stop</div>
        <div class="btn" id="reset" v-on:click="reset()" v-bind:class="{inactive: isReset}">Reset</div>
      </div>
    </div>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    const vm = new Vue(
      {
        el: '#main',
        data: {
          time: "00:00:000",
          startTime: 0,
          erapsedTime: 0,
          isStart: false,
          isStop: true,
          isReset: true,
          timeoutId:0,
        },

        methods: {
          countUp() {
            const d  = new Date(Date.now() - this.startTime + this.erapsedTime);
            const m  = String(d.getMinutes()).padStart(2, '0');
            const s  = String(d.getSeconds()).padStart(2, '0');
            const ms = String(d.getMilliseconds()).padStart(3, '0');

            this.time = `${m}:${s}:${ms}`;
            this.timeoutId = setTimeout(() => {
              this.countUp();
            }, 10);
          },

          setButtonStateInitial() {
            this.isStart = false;
            this.isStop = true;
            this.isReset = true;
          },
          setButtonStateRunning() {
            this.isStart = true;
            this.isStop = false;
            this.isReset = true;
          },
          setButtonStateStopped() {
            this.isStart = false;
            this.isStop = true;
            this.isReset = false;
          },

          start() {
            if (this.isStart) {
              return;
            }
            this.setButtonStateRunning();
            this.startTime = Date.now();
            this.countUp();
          },
          stop() {
            if (this.isStop) {
              return;
            }
            this.setButtonStateStopped();
            clearTimeout(this.timeoutId);
            this.erapsedTime += Date.now() - this.startTime;
          },
          reset() {
            if (this.isReset) {
              return;
            }
            this.time = '00:00:000';
            this.setButtonStateInitial();
            this.erapsedTime = 0;
          }
        },
      }
    );
  </script>
</body>

</html>

 

 

 

 

 

 

COMMENT

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です