【Vue.js 3】プラグインでスコープを超えてsetIntervalを操作する

VueでSPAアプリ作成中の初学者による備忘録です。
setIntervalを使用したtimer機能を実装した際にページ遷移時にsetIntervalが裏で実行され続けてしまうエラーの対処に手こずったので記事として残します。

参考ページ：https://tech.smartcamp.co.jp/entry/2019/08/08/185351

#Vue template

templateの記述は下記のとおりです。progressタグはバーを表示するタグでspanタグ内ではプログレスバーが減るのと同時に数字でもカウントダウンを表示するように記載しています。


```
<template>
 <div>
     <progress id="progress" value="45" max="45"></progress>
     <span id="countdown">45</span>
 </div>
</template>

```

#当初問題が発生したコード

```
methods: {
setTimer: funcition() {
 let i = 45;

    function count(){
      i -- ;
      const doc0 = document.getElementById('countdown');
      doc0.textContent= `${i}`;
      document.getElementById('progress').setAttribute('value', i);
      if(i == 0 ) {
        clearInterval(intervalId);
      }
    }
    const intervalId = setInterval(count, 1000);
 }
}

```

この書き方はタイマーを表示したいページでは正常に動作します。
しかし、カウントダウンの数字が0になる前にページ遷移をすると「clearInterval」が実行されずに裏で「setInterval」が実行され続け、1秒ごとにエラーが発生してしまいます。

ここで私が躓いたのがclearIntervalは引数が必要で引数には実行されているsetIntervalを定数化したものを代入しなければいけないという点です。

しかし、遷移先のページで「clearInterval(intervalId)」とすると当然ながらintervalIdにはスコープ外で定義された「count」という関数が含まれるためこれでは動作しません。
従って、setIntervalをコンポーネント間で共通して操作できるようにする必要がありました。

そこで当記事の冒頭で紹介したサイトでプラグインを用いる方法が記載されており、そちらを利用したところ解決できました。
※参照サイトはVue.js2の書き方だったのでVue.js3用に少しアレンジしました。

#プラグイン ファイルの追加

src/plugins/SetInterval/index.js

```
export default {
  install (app) {
    app.config.globalProperties.$intervals = []
    app.config.globalProperties.$setInterval = (func, intervalMilliSec) => {
      const timerId = setInterval(() => {
        func()
      }, intervalMilliSec)
      app.config.globalProperties.$intervals.push(timerId)
      return timerId
    }
    app.config.globalProperties.$clearInterval = (timerId) => {
      clearInterval(timerId)
      app.config.globalProperties.$intervals =[]
    }
  }
}


```

```applications.js
import SetInterval from '../src/plugins/SetInterval';

const app = createApp(App);
app.use(SetInterval);
```

app.config.globalProperties.`$`intervalsはコンポーネント上でthis.$intervalsとして呼び出すことができます。

[プラグインファイルで行われていること]

setIintervalの設定
① app.config.globalProperties.$intervals = []

$intervalsの中身の初期設定を行う。

②  app.config.globalProperties.$setInterval = (func, intervalMilliSec) => {
      const timerId = setInterval(() => {
        func()
      }, intervalMilliSec)

カウントを行う関数と時間を引数として渡せる$setIntervalを定義する。
実行されるsetInterval関数はtimerIdとして保存する。

③ app.config.globalProperties.$intervals.push(timerId)
      return timerId
    
timerIdに代入されたsetInterval関数を①の$setIntervalsに代入する。

clearIntervalの設定
①app.config.globalProperties.$clearInterval = (timerId) => {
      clearInterval(timerId)

引数として渡されたsetIntervalを終了させる$clearIntervalを定義しています。

② app.config.globalProperties.$intervals =[]

clearInterval後は$setIntervalsを初期化し、再び同じ動作を行えるようにしています。
      

#component上での実装

```
export default {
     beforeRouteLeave (to, from, next) {
       next(this.$clearInterval(this.$intervals))
     },

 mounted() {
      this.setTimer();
    },
methods: {
setTimer: function() {
        let i = 45;
        this.$setInterval(() => {
          i -- ;
          const doc0 = document.getElementById('countdown');
          doc0.textContent= `${i}`;
          document.getElementById('progress').setAttribute('value', i);
          if(i == 0 ) {
            clearInterval(this.$intervals);
          }
      }, 1000)
     }
}

}

```

setTimerでは`$`setIntervalに原案のコードで言うところのcount関数を渡しています。
$setIntervalはmethods外でも呼び出すことができます。

ページ遷移時にclearIntervalが実行できれば良いので、
beforeRouteLeave (to, from, next) {
       next(this.$clearInterval(this.$intervals))
     },
とすることでその動作を実現できます。