ブラウザ内でゴリゴリDomをいじるアプリを作らないといけなくなったので今まで避けてきたクライアントサイドのフレームワークをいろいろ触ってみたところ、Riot.jsが各所で評判もよく、一番楽しかった。
勉強がてらサンプルアプリを作ってみたのでその解説を記す。
#作ったもの
値入力(a,b) |
|
||
こんな構成で上記それぞれの四角がコンポーネントであり、入力値の変更を受けて結果が自動的に変わるようなものを作った。
#見た目をどうするか
見た目は大事。
しかしながらデザインセンスがないのでいつも使ってるBootstrapを使った。
レスポンシブにもなるし。
スキンはbootstrap-material-designを利用した。
#コンポーネント間の通信をどうするか
Riot.jsには標準で使い勝手のよいriot-observableがついているが、コンポーネント間の直接依存は避けたいため、fluxのようなものを自作しようしようかとしていたが、aggreさんのObseriotというライブラリがとてもいけていたので利用させてもらった。
今回の通信仕様
- ルートコンポーネント
- Actions :
- 数値の変更Action
- Stores :
- 足し算結果Store
- 掛け算結果Store
- Dispatcher:
- 数値の変更Actionをlisten → 足し算 → 足し算結果Storeに格納 → 足し算結果Store変更を通知
- 数値の変更Actionをlisten → 掛け算 → 掛け算結果Storeに格納 → 掛け算結果Store変更を通知
- Actions :
- 値入力コンポーネント :
- 入力内容に変更があったら数値の変更ActionをDispatcherに通知
- 足し算結果コンポーネント:
- 足し算結果Storeをlisten → 表示内容変更
- 掛け算結果コンポーネント:
- 掛け算結果Storeをlisten → 表示内容変更
コード
ルートコンポーネント(index.html)
<body>
<div class="container-fluid main">
<div class="row">
<div class="col-md-offset-1 col-md-10">
<my-navbar></my-navbar>
<div class="row">
<div class="col-sm-6">
<my-frame id="input" caption="入力">
<my-input></my-input>
</my-frame>
</div>
<div class="col-sm-6">
<div class="row">
<div class="col-sm-12">
<my-frame id="sum" caption="a + b">
<my-sum></my-sum>
</my-frame>
</div>
<div class="col-sm-12">
<my-frame id="product" caption="a * b">
<my-product></my-product>
</my-frame>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
var action = obseriot.action;
var store = obseriot.store;
obseriot.defineAction(
"input", // action name
/* @param {{ a: number, b: number }} nums */
function(nums) { // handler action
return nums;
}
);
obseriot.defineStore(
"sum", // sotre name
function () { // handler action
return store.sum.state
},
0 // default state
);
obseriot.defineStore(
"product", // sotre name
function () { // handler action
return store.product.state
},
0 // default state
);
// アクションの監視 → ストア更新 → ストア更新通知
obseriot.listen( action.input, function (nums) {
store.sum.state = nums.a + nums.b;
obseriot.notify( store.sum );
});
obseriot.listen(action.input, function (nums) {
store.product.state = nums.a * nums.b;
obseriot.notify( store.product );
});
riot.mount('my-navbar');
riot.mount('#input');
riot.mount('#sum');
riot.mount('#product');
</script>
</body>
HTMLはBootStrapを使ってるとDIVの階層がとんでもになってしまうが、コンポーネント分割によって構造だけをあらわせるようになった。
配置調整も後からでもしやすい。
obseriot(ちょっとだけ拡張)によりActionとStoreの記述が非常に簡潔。
値入力コンポーネント
<my-input>
<div class="row">
<div class="col-xs-offset-1 col-xs-8">
<form class="form-horizontal">
<div class="row">
<div class="form-group label-floating" >
<label class="control-label">a</label>
<input name="a" type="text" class="form-control" oninput="{notify}">
</div>
</div>
<div class="row">
<div class="form-group label-floating" >
<label class="control-label">b</label>
<input name="b" type="text" class="form-control" oninput="{notify}">
</div>
</div>
</form>
</div>
</div>
this.notify = function(e) {
obseriot.notify(action.input, {
a: Number(this.a.value),
b: Number(this.b.value)
})
};
this.on('mount', function() {
$.material.init();
})
</my-input>
各inputのonInput時にディスパッチャに数値の変更Actionを通知
注)materialの初期処理はindex.htmlの最後におくとダメで、各コンポーネントのonMountで実施したらうまくいった。この方法が適切かどうかは未調査
結果表示コンポーネント
<my-sum>
<div class="row">
<h1 class="col-xs-2">=</h1><h1 class="col-xs-10">{sum}</h1>
</div>
sum = store.sum.state;
var self = this;
obseriot.listen(store.sum, function() {
self.sum = store.sum.state;
self.update();
})
</my-sum>
表示値の初期値はストアの初期値
ストア変更をlistenし、通知を受けたら表示内容を変更する
UI外からの通知のためthis.update()をしないと反映されないことに注意
フレームコンポーネント
<my-frame>
<div class="well">
<h4>{opts.caption}</h4>
<yield/>
</div>
</my-frame>
こういう全体の見た目に関わる部分もコンポーネント化できるのはありがたい。
記述方法もいたって簡潔。
<yield/>
の中に利用側が入れた子供が配置される。
普通のBootStrap画面が問題なく表示できた。
#デモおよびソース
http://embed.plnkr.co/yyki9H/
Plunkerは初めて利用したが便利。
#所感
JS界隈のフレームワークはそれなりに覚えることが多く、また移り変わりが激しいので避けてきたが、このRiot.jsはコンセプトが単純なのですんなり入れた。
BootStrapとの相性も悪くないことが確認できたのでしばらく付き合っていこうと思う。
#2017/1/4追記
デモが動かなくなってたので確認したらメジャーアップしてAPIが変わってたので下記修正。
-<script src="https://rawgit.com/riot/riot/master/riot%2Bcompiler.min.js"></script>
+<script src="https://rawgit.com/riot/riot/v2.6.7/riot%2Bcompiler.min.js"></script>
master参照はよろしくないね。。。
反省しました。
3.X版はそのうち作る。