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) { /* */ }) ...Ariot.route('/fruit/apple', function() { /* */ }) ...B riot.route('/fruit/orange', function() { /* */ }) ...C riot.route('/fruit/*', function(name) { /* */ }) ...Driot.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>
