Riot.js tips - preventUpdate を使ってスクロール時の大量 update() を止めよう

phi phi on Riot.js

yield』 に引き続きマニアックな Riot.js tips です.

先日下記のようなツイートが流れてきました.

もれなく私も昔これにハマったことがあったので, その対処法を紹介したいと思います.

デモサンプル

まずはデモです.

スクロールするとコンソールに updated と表示されます. 大量に update() されないよう対応しているのでそれほどたくさん表示されないのがわかるかと思います.

Riot.js におけるイベント発火時のライフサイクル

Riot.js は, onほにゃらら に設定している関数を呼ぶと自動で update を呼ぶ仕様になっています.

onclickontouchstart といったそれほど頻繁に発火しないイベントならそれでも良いのですが, onscrolloninput といった短時間に大量に発火するようなイベントの場合, かなりの負荷になってしまいます.

Riot.js の独自イベントに含まれる preventUpdate を使おう

Riot.js で登録した関数が実行された際に渡されるイベントオブジェクトには
preventUpdate というフラグが用意されています.

実はこの値が false のときのみ裏側で update() が走るようになっています.

Riot.js 内の実装コードはこちら

    if (!e.preventUpdate) {
      el = item ? getImmediateCustomParentTag(ptag) : tag
      el.update()
    }

関数実行後に上の処理が呼ばれ preventUpdate の値によって update() するかどうか分岐しているのがわかるかと思います.

デフォルト値は false なのでこの値を true にすれば update() が呼ばれなくなります.

<div class='box' onscroll='{scroll}'></div>  
this.scroll = function(e) {  
  e.preventUpdate = false;
}

一定間隔で明示的に update() を呼ぼう

上記の対応を入れただけだと update() を全く呼んでくれなくなるのでそれはそれで困りますよね. Riot.js では明示的に update() メソッドを実行することでライフサイクルをコントロールすることができます.

setTimeout() と組み合わせることで update() の頻度を劇的に下げることができます.

コードは下記のようになります.

this._scrollTimeoutId = null;  
this.scroll = function(e) {  
  // 自動 update を off る
  e.preventUpdate = true;

  if (this._scrollTimeoutId) {
    window.clearTimeout(this._scrollTimeoutId);
  }
  this._scrollTimeoutId = window.setTimeout(function() {
    // ここで明示的に update を呼ぶ
    self.update();
    self._scrollTimeoutId = null;
  }, 32);
};

ちょっと複雑に見えますがやっていることは単純です.

setTimeout() の id を保持しておいて, 一定間隔(今回は32ミリ秒)以内にもう一度イベントが発火した場合 前の処理をキャンセルして再度 setTimeout() で処理を登録しなおしています.

こうすることで一定間隔以内に連続で発生したイベントをすべてキャンセルし, 最後のイベント時のみ update() を実行することができます.

デモのコード

デモの Riot.js 部分のコードです.

<app>  
  <h1>Riot.js tips - preventUpdate を使ってスクロール時の大量 update を止めよう</h1>
  <div class='box', onscroll='{scroll}'>
    <div class='pad'>pad</div>
  </div>

  <style>
  .box {
    width: 200px;
    height: 200px;
    background-color: white;
    box-shadow: 0px 0px 4px 0px #aaa;
    overflow: scroll;
  }
  .pad {
    height: 1200px;
    padding: 4px;
  }
  </style>

  var self = this;
  this.title = opts.title;

  this._scrollTimeoutId = null;
  this.scroll = function(e) {
    // 自動 update を off る
    e.preventUpdate = true;

    if (this._scrollTimeoutId) {
      window.clearTimeout(this._scrollTimeoutId);
    }
    this._scrollTimeoutId = window.setTimeout(function() {
      // ここで明示的に update を呼ぶ
      self.update();
      self._scrollTimeoutId = null;
    }, 32);
  };

  this.on('updated', function() {
    console.log('update しました!');
  });

</app>  

この処理を応用することで input search で入力が落ち着いたタイミングで検索をかける, なんてこともできます.

それも次回サンプルを交えて紹介したいと思います.