Riot.js tips - preventUpdate を使ってスクロール時の大量 update() を止めよう
『yield』 に引き続きマニアックな Riot.js tips です.
先日下記のようなツイートが流れてきました.
ちょっとこれは驚愕の事実かも...riotjsでタグで直接onscroll="{hoge}"ってかたちでbindするじゃん?スクロールする度に、updatedが呼ばれる。。。つまり、スクロールする度にupdate()がかかってる。。。!?
— 杏z (@AnzNetJp) 2016年10月27日
もれなく私も昔これにハマったことがあったので, その対処法を紹介したいと思います.
デモサンプル
まずはデモです.
スクロールするとコンソールに updated
と表示されます.
大量に update()
されないよう対応しているのでそれほどたくさん表示されないのがわかるかと思います.
Riot.js におけるイベント発火時のライフサイクル
Riot.js は, onほにゃらら
に設定している関数を呼ぶと自動で update
を呼ぶ仕様になっています.
onclick
や ontouchstart
といったそれほど頻繁に発火しないイベントならそれでも良いのですが,
onscroll
や oninput
といった短時間に大量に発火するようなイベントの場合, かなりの負荷になってしまいます.
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 で入力が落ち着いたタイミングで検索をかける, なんてこともできます.
それも次回サンプルを交えて紹介したいと思います.