SMARTCAMP Engineer Blog

スマートキャンプ株式会社(SMARTCAMP Co., Ltd.)のエンジニアブログです。業務で取り入れた新しい技術や試行錯誤を知見として共有していきます。

Vue.jsで定期的にバックエンドと通信したいときに気にしたい3つのこと - プラグイン作成で解決

スマートキャンプのエンジニア瀧川です!

クライアントサイド(JavaScript)で処理を定期実行したい場合は皆さん使いますよね! そうsetInterval関数です。

ただ何も考えず使ってしまうと色々な問題が起こったり...

そこで本記事ではsetInterval関数を使う際の困りごとを挙げて、それをまるっと解消するVue.jsプラグインを作る方法を紹介したいと思います!

(今回はVue.jsで実装しますが、特に依存しているわけはないので他のフレームワークをお使いの方も参考にしてください!)

まずVue.jsプラグインの雛形を作る

今回はVue.jsでsetIntervalのラッパー関数をプラグインとして実装しようと思います。

プラグインは以下のようになります。

src/plugins/SetInterval/index.js

export default {
  install (vue) {
    vue.prototype.$setInterval = (func, intervalMilliSec) => {
      const id = setInterval(() => {
        func()
      }, intervalMilliSec)
      return id
    }
  }
}

これをmain.jsなどVue読み込み時に以下のようにインストールすることで実行可能になります。

main.js

import Vue from 'vue'
import SetInterval from '@/plugins/SetInterval'
...
Vue.use(SetInterval)

コンポーネント内での実行イメージは以下のようになります!

export default {
  created () {
    this.$setInterval(() => {
      // 処理
    }, 1000)
  }
}

src/plugins/SetInterval/index.js に困りごとに応じた機能を追加していこうと思います!

困りごと1

困りごと: ブラウザ(タブ)を開きっぱなしにすると必要以上に実行されてしまう

これがもっともよくある問題かなと思います。

setIntervalは通常ブラウザが起動している状態であれば実行され続けます。

例えば他のタブで別サイトも見ている場合なんかに、裏側で実行され続けると不必要にAPIリクエストを投げ続けてしまう問題が起こります。(タブを複製した場合にその分APIリクエストが流れる...とか負荷がばかにできなくなってきますよね)

あと、ページを閲覧しているときだけカウントアップ...なんてこともあるかもしれませんね。

解決法: Page Visibility APIを利用してアクティブなときにしか処理を実行しない

Page Visibility APIを利用することで、JavaScriptからページがアクティブかどうかを取得でき、また状態が変更されたイベントをハンドリングすることができるようになります!

document.visibilityStatevisible の状態が、ページ(タブ)がアクティブな状態となり、その状態のみ関数が実行されるようにしています。

export default {
  install (vue) {
    vue.prototype.$setInterval = (func, intervalMilliSec) => {
      const id = setInterval(() => {
        if (document.visibilityState === 'visible') {
          func()
        }
      }, intervalMilliSec)
      return id
    }
  }
}

困りごと2

困りごと: ページ遷移してもsetIntervalが維持されてしまう

Vue.jsなどフレームワークを使っている場合、多くはクライアントサイドでルーティングなど管理していることと思います。

その場合、ページ遷移時にsetIntervalも維持されてしまうので、意図しないページで処理が実行されてしまうことが起こり得ます。

例えば、ログアウトしてログイン画面に遷移させたのに、裏ではAPIにリクエストを投げ続けてしまう...なんてことが起きるかもしれません。

そんなときに当然キャンセル処理を実装すれば解決!となりそうですが、setIntervalのキャンセルは以下のように返り値の timerId をclearInterval関数に渡す必要があり、様々のコンポーネントで好き勝手に呼び出している場合はそれも困難になります。

const timerId = setInterval(() => {
  // 処理
})
...
clearInterval(timerId)

解決法: timeId(setIntervalのID)をプラグイン側で一括管理

以下のように各setIntervalのIDをインスタンス変数として保持し、それを使ってすべてをキャンセルするclearAllIntervals関数などを定義しています。

これによって、ページ遷移時のイベントをハンドリングして、setIntervalをキャンセルするなど可能になります!

export default {
  install (vue) {
    vue.prototype.$intervals = []
    vue.prototype.$setInterval = (func, intervalMilliSec) => {
      const id = setInterval(() => {
        func()
      }, intervalMilliSec)
      vue.prototype.$intervals.push(id)
      return id
    }
    vue.prototype.$clearInterval = (id) => {
      clearInterval(id)
      vue.prototype.$intervals = vue.prototype.$intervals.filter(i => i !== id)
    }
    vue.prototype.$clearAllIntervals = () => {
      vue.prototype.$intervals.forEach(clearInterval)
      vue.prototype.$intervals = []
    }
  }
}

困りごと3

困りごと: 開発環境(ローカル)で定期実行されるとデバッグなどしにくい

APIの開発をしている際に、別の機能で定期実行でAPIを叩かれて、ログが流れてしまう... みたいなことがありますよね。

解決法: 環境変数に応じて定期実行しない

あらかじめ開発環境(ローカル)で VUE_APP_DISABLE_SET_INTERVAL などの環境変数を定義しておいて、その変数が定義されている場合setIntervalを無効化しています。

こうすることで、簡単に切り替えが可能になります!

export default {
  install (vue) {
    vue.prototype.$setInterval = (func, intervalMilliSec) => {
      if (typeof (process.env.VUE_APP_DISABLE_SET_INTERVAL) !== 'undefined') {
        console.log(`[DISABLE_SET_INTERVAL] Check environment vars`)
        return null
      }
      const id = setInterval(() => {
        func()
      }, intervalMilliSec)
      return id
    }
  }
}

プラグイン全体

export default {
  install (vue) {
    vue.prototype.$intervals = []
    vue.prototype.$setInterval = (func, intervalMilliSec) => {
      if (typeof (process.env.VUE_APP_DISABLE_SET_INTERVAL) !== 'undefined') {
        console.log(`[DISABLE_SET_INTERVAL] Check environment vars`)
        return null
      }
      const id = setInterval(() => {
        if (document.visibilityState === 'visible') {
          func()
        }
      }, intervalMilliSec)
      vue.prototype.$intervals.push(id)
      return id
    }
    vue.prototype.$clearInterval = (id) => {
      clearInterval(id)
      vue.prototype.$intervals = vue.prototype.$intervals.filter(i => i !== id)
    }
    vue.prototype.$clearAllIntervals = () => {
      vue.prototype.$intervals.forEach(clearInterval)
      vue.prototype.$intervals = []
    }
  }
}

まとめ

今回はsetIntervalを使う際に起こる問題を解決するVue.jsプラグインの実装例を紹介いたしました。

解決法を簡単にまとめると以下のようになります。

  • Page Visibility APIを使う
  • setIntervalをグローバルで管理する
  • 開発モードを実装する

こういった細かい問題は、優先度が低く、ボディーブローのようにじわじわコストやリスクとなっていくので、参考になればうれしいです!