Tagbangers Blog

Riot.jsのRoutingについて

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>