SIerだけど技術やりたいブログ

5年目のSIerのブログです

Vuej.sでSPAを実現するときは注意してsetIntervalを使おう

setIntervalとは

一定の遅延間隔を置いて関数を繰り返し実行したいときに利用する。例えばポーリング。
developer.mozilla.org

1秒ごとにコンソール出力する例。

<!doctype html>
<html lang="ja">
<script>
  window.setInterval(function(){
    console.log("polling");
  }, 1000);
</script>
  <body>
  </body>
</html>

setIntervalの生存期間

リファレンスには特に記述がなかった。
6.3 Timers — HTML5
WindowOrWorkerGlobalScope.setInterval() - Web API インターフェイス | MDN

動作確認してみたら以下のようになった。

  • ページ内リンクやJSでの動的なDOM書き換え、はsetIntervalがクリアされない
  • ページ外リンク、はsetIntervalがクリアされる

以下、検証内容

Google Chromeバージョン: 60.0.3112.90で動作確認。

<!doctype html>
<html lang="ja">
<script>
  window.onload = function () {
    window.setInterval(function() { console.log("hi"); }, 1000);
    document.getElementById('btn').addEventListener('click', function() {
        var parent = document.getElementsByTagName('body')[0];
        while(parent.firstChild) parent.removeChild(parent.firstChild);
        var newElm = document.createElement('div');
        newElm.textContent = 'hello world';
        parent.appendChild(newElm);
    });
  }
</script>
  <body>
    <a href="#xxx">ページ内リンク</a>
    <a href="other.html">ページ外リンク</a>
    <button id="btn" type="button">JSで動的に書き替える</button>
    <p>dummy</p>
    ...
    <p>dummy</p>
    <p id="xxx">xxx</p>
    <p>dummy</p>
    ...
    <p>dummy</p>
  </body>
</html>
<!doctype html>
<html lang="ja">
<body>
<a href="polling.html">戻る</a>
</body>
</html>

f:id:kimulla:20170811110448g:plain

ということで、Chromeだと、DOMをunloadして次のDOMをloadするまで(=Documentオブジェクトと同じ生存期間)っぽい。
WindowオブジェクトとDocumentオブジェクトの関係はここが詳しかった。
Window オブジェクトや Document オブジェクト、DOMなど | Web Design Leaves


Vue.jsでsetIntervalを使うときの注意点

SPAの場合、データのみリクエストしてDOMを差分更新することになる。そのため、Documentオブジェクトの入れ替えが起こらず、一度setIntervalしたタイマは、全画面で有効になり続ける。

以下、検証内容

ポーリングしたいページ。

<template>
  <div>
    <h1>polling</h1>
   <router-link to="/goodbye">Goodbye</router-link>
  </div>
</template>

<script>
export default {
  name: 'hello',
  mounted () {
    setInterval(function () {
      console.log('hi')
    }, 1000)
  }
}
</script>


ポーリングしたくないページ。

<template>
  <div>
    <h1>ポーリングしたくないページ</h1>
  </div>
</template>

<script>
export default {
  name: 'boodbye'
}
</script>

f:id:kimulla:20170811111832g:plain

問題点と解決方法

この場合無駄なネットワークコストがかかるため、SPAでsetIntervalする際は明示的にclearIntervalを利用してタイマを削除したほうがよい。

window.clearInterval - Web API インターフェイス | MDN

ページを遷移する際にclearIntervalを呼ぶためには、ライフサイクルフックを利用できる。

jp.vuejs.org

ということで、以下のコードで、ページ遷移する際にポーリングを止められる。

<template>
  <div>
    <h1>polling</h1>
   <router-link to="/goodbye">Goodbye</router-link>
  </div>
</template>

<script>
export default {
  name: 'hello',
  data: function () {
    return {
      intervalId: undefined
    }
  },
  mounted () {
    this.intervalId = setInterval(function () {
      console.log('hi')
    }, 1000)
  },
  beforeDestroy () {
    console.log('clearInterval')
    clearInterval(this.intervalId)
  }
}
</script>

f:id:kimulla:20170811112524g:plain

結論

Vue.js(というかSPA全般)でsetIntervalするときは、ちゃんとclearIntervalしないとダメ。


p.s.
setIntervalの生存期間調べるためにWebKitのソース読もうとしたんだけど、C++難しすぎて1日頑張って諦めた。C++力がほしい。