SPAをとりあえず味わってみたいけどどこからはじめればいいか。
まずはやさしく始められるRiotでやってみよう。
ということでRiotのRoutingについてドキュメント引きながらやってみました。
Riot Routerの機能
いわゆるRouting、URLを判断してどのビューを表示させるか決めるというのが大きな仕事
Setup
Routingの書き方は大きく2つ
riot.route(callback)
URLが変化したらcallbackを返します。
*「URLが変化」するというイベントが発生するタイミング
とは以下の4つのパターンのとき
- 新しい#(URL)がアドレスバーに入力されたとき。
(#が必要な理由はデフォルトの仕様によるため。後述します) - ブラウザの戻る/進む ボタンが押されたとき。
- riot.route(to) が呼ばれたとき。
リンクをクリックする遷移などではなく、自分でaaa.tagのビューに飛ばす、などプログラム的に遷移させたいときに使う - リンクがクリックされたとき。
ドキュメンテーションの例はちょっとわかりづらいのですが、引数はいくつでも取れますので実際は
riot.route((...args) => { });
と同じ
たとえばURLが / から /#/vegetables/1 というように変化すると
riot.route((...args) => { console.log(args); // ["vegetables", "1"] });
みたいに引数はパスごとに配列に入ります
riot.route(filter, callback)
filterの条件に該当するURLでアクセスされたらcallbackを返します。
riot.route('/vegetables', (...args) => { riot.mount('#page', 'vegetable-index'); }); /* /#/vegetables というアクセスがきたらvegetable-indexをマウントする */
のように使います。
Filterは以下の記号も使えます。
*
:([^/?#]+?) と同じ
..
:.* と同じ
riot.route('/vegetables..', (...args) => { riot.mount('#page', 'vegetable-index'); }); /* たとえば /#/vegetables /#/vegetables/1 /#/vegetablesasaki というアクセスがきたらこのfilterにひっかかるので、vegetable-indexをマウントする */
# おまけ: ... は引数をとってくれない
riot.route('/vegetables/*', () => { console.log(args); /* argsは/vegetables/以降スラッシュで区切ったパスが配列で入る たとえば /vegetables/carrot → args = ["carrot"] */ }); riot.route('/vegetables..', (...args) => { console.log(args); // argsにはなにも入らない });
Routing の優先度
上記のようにURLとview(tag)の紐づけを行っていくのですが、Riotではどのような順番でURLをマッチさせようとするのでしょうか。
riot.route(function(name) { /* */ }) ...A
riot.route('/fruit/apple', function() { /* */ }) ...B riot.route('/fruit/orange', function() { /* */ }) ...C riot.route('/fruit/*', function(name) { /* */ }) ...D
riot.route('/fruit/peach', function(name) { /* */ }) ...E
ポイントは以下です
- RiotのRouterは上から順にURLのマッチングを行い、マッチした一番の初めのRoutingを適用する。
- FilterなしはFilterありより優先度が低い。
ということでAがはじめに書いてあっても優先度は最下位になる
なので上記の例でいくと、仮に /fruit/peach でアクセスされると、 B > C > D > E > A の優先度順でマッチングを行い、
Eより先に/fruit/* のDにマッチしてしまうのでRouting Dが適用されます。
複数のRoutingは適用できないのか?
先の例でも、/fruit/peachでアクセスすると、マッチングだけでいうとA, D, Eのルーティングの条件にひっかかりしました。ですが最終的には優先度の一番高いDしか適用されません。
1つのルーティングは1つしか適用されません。では2階層以上のメニューを持つSPAのルーティングを考えてみます。
こんなかんじで上にグローバルメニューがあって、左に各ページのナビゲーションがあります。ページBの下はB-1,B-2,B-3,Cの下はC-1,C-2,C-3...という形になっているとします。
この場合A-1,A-2,A-3のどのページにアクセスしても、サイドメニューの並びは変わらない(A-1~A-3で構成されている)ので、この間での行き来ではナビもリフレッシュしたくありません。
コンポーネントはこのように作りました。
このときRoutingは
riot.route('/a/1', (...args) => { riot.mount('#page', 'a-detail', { id : "1" }); }); riot.route('/a/2', (...args) => { riot.mount('#page', 'a-detail', { id : "2" }); }); riot.route('/a/3', (...args) => { riot.mount('#page', 'a-detail', { id : "3" }); }); riot.route('/a..', (...args) => { riot.mount('#content', 'a-index'); });
としたくなります。 /a → /a/1 という遷移であれば、意図した形で表示されます。
これは /a にアクセスした時に a-indexがmountされ、/a/1にアクセスすると a-detailがmountされるためです。
しかし /b → /a/1 (直リン) の時にサイドナビが切り替わってくれません。サイドはB-1,B-2,B-3のリストが出たままA-1のページと表示されます。
これは /b にアクセスすると b-indexはmountされ、/a/1にアクセスするとa-detailがmountされますが、a-indexはmountされないからです。
/#/fruits → /#/vegetables/1 にアクセスした結果
とすると A-1のページを表示するときは a-indexとa-detailがmountされている必要があります。
riot.route('/a..', {...})とriot.route('/a/1', {...})のRoutingどちらも適用させたい…、でも1つのRoutingは1つしか適用できない、ならばRoutingを複数持てばいいじゃない
というのがRouting Groupです。先ほどのコードをこうします
riot.route('/a..', (...args) => { riot.mount('#content', 'a-index'); }); var subRoute = riot.route.create(); subRoute('/a/1', (...args) => { riot.mount('#page', 'a-detail', { name : "1" }); }); subRoute('/a/2', (...args) => { riot.mount('#page', 'a-detail', { name : "2" }); }); subRoute('/a/3', (...args) => { riot.mount('#page', 'a-detail', { name : "3" }); });
riot.route.create()することで、新しいRouting(のコンテキスト、お品書き)を作ることができるということになります。
そすると別々のRoutingとして扱えるので、riot.route('/a..', {...})とsubRoute('/a/1', {...})のどちらも適用することができます。
この状態で
/#/fruits → /#/vegetables/1 にアクセスした結果、vegetable-indexもmountされなおし
サブナビがくだもの一覧からやさい一覧に変わっています。
サンプルプログラム
やさいのとこしかちゃんと動かしてませんが。。
https://github.com/bourbonizable/riot-routing-sample
ベースパスの変更などは、また次回書きます。
# おまけ:tag直下の要素は必ず何らかのタグで囲まなければならない
これハマりました。sample.tagをmountした時にReferenceErrorが出る
<sample> aaaa </sample>
修正方法は以下です。
<sample> <p>aaaa</p> <!-- spanでもh1でも何でも良いのでくくる --> </sample>