何が起きるのか
Riot.jsの <script>...</script>
はマウントされるたびに動くというところにハマって、注意しないとobservableなイベントを複数回処理してしまうよというお話です。
サンプルコードはこちらです。
https://github.com/NewGyu/riot-sample/tree/obseriot/duplex-events
本稿ではriot-observableを薄くラップしたobseriotというライブラリを使っていますが、この事象はobseriot固有のものではありません。Singletonなobservableオブジェクトを使うとハマりやすい問題です。
体験してみよう
イベントを発する(publishする)ものを定義する
下記のHumanStore
はstartLife
メソッドを実行すると、
- まず
Born
イベントをpublish - その後1秒おきに
Living
イベントをpublish - そして5秒経つと
Dead
イベントをpublishして動きを止めます
import ob from "obseriot";
export const HumanStore = new class HumanStore {
constructor() {
}
startLife() {
ob.notify(HumanEvent.Born);
const t = Date.now();
const intervalId = setInterval(()=>{
if(Date.now() > t + 5000) {
clearInterval(intervalId);
ob.notify(HumanEvent.Dead);
return;
}
ob.notify(HumanEvent.Living);
} , 1000);
}
}
export const HumanEvent = {
Born: {
handler: {
name: "human.born",
action: () => {}
}
},
Living: {
handler: {
name: "human.living",
action: () => {}
}
},
Dead: {
handler: {
name: "human.dead",
action: () => {}
}
}
}
イベントを受ける(subscribeする)タグを定義する
下記のpage1がmountされるとHumanStore.startLife
が実行されます。
そして、HumanEventを受け取るとコンソールにログ出力します。
import ob from "obseriot";
import {HumanStore, HumanEvent} from "../../lib/Human";
<page1>
<p>男の人が額縁の絵を壁にかけようとしています。</p>
<figure class="image">
<image src="img/kaiga.png">
</figure>
<script>
ob.listen(HumanEvent.Born, ()=>{
console.log("He was born");
});
ob.listen(HumanEvent.Living, ()=>{
console.log("He is living");
});
ob.listen(HumanEvent.Dead, ()=>{
console.log("He was Dead");
});
this.on("mount", ()=>{
HumanStore.startLife();
});
</script>
</page1>
ルーティングする
さて親コンポーネントで次のようなルーティングを設定します。
<app>
<article class="section">
<page class="container"></page>
</article>
<script>
route("/page1", () => {
this.activePage = "page1";
this.subtitle = "絵のページ";
this.update();
riot.mount("page", "page1");
});
route("/page2", () => {
this.activePage = "page2";
this.subtitle = "音楽のページ";
this.update();
riot.mount("page", "page2");
});
route.start(true);
</script>
</app>
上記は省略しているので全文はこちらのソースを参照してください。
動作を確認する
npm start
して、 http://localhost:8080 にアクセスしてみます。
上記のようなページが出るので、Chromeの開発者ツール等でconsole.log の様子を見てみましょう。
mountと同時にHumanStore.startLife
が実行されるのでこのようにログ出力されます。
さて、page2に切り替えて、もう一度page1に戻してみましょう。
なんだか2回ずつログが記録されていますね。
page1,page2を何度も何度も切り替えるとログ出力される回数がどんどん増えていきます。
なぜこうなるのか
冒頭に述べたとおり、
Riot.jsの
<script>...</script>
はマウントされるたびに動く
からなんですね。
マウントされるたびに ob.listen
がcallされるので、イベントハンドラコールバックが複数登録されてしまうためです。
どうしたら良いのか
今のところunmount時にイベントハンドラコールバックの登録を解除することしか思いつきません。
this.on("unmount", ()=>{
ob.remove(HumanEvent.Born);
ob.remove(HumanEvent.Living);
ob.remove(HumanEvent.Dead);
});