API · ルータ
デフォルトの状態でRiot.jsにルータはバンドルされていません。これはニーズに合わせて好きなルータライブラリを選べるようにするためです。
私たちが開発したriot-route
についても、今後引き続きメンテナンスを続けていきます。この小さなルータライブラリは、独立のモジュールとして使うことができ、またRiotのミニマルな哲学に非常にフィットしています。
riot-route
を使うには、あなたのプロジェクト内で<script>
タグを使って組み込むか、
<script src="path/to/dist/route.js"></script>
あるいは、ES6の文法を使っているなら次のようにするだけです。
import route from 'riot-route' // var route = require('riot-route') is also ok
API
Riotルータは最もミニマルなルータの実装であり、以下の機能を含みます。
- pushStateとヒストリAPI
- 複数のルーティンググループ
- 交換可能パーサ
- isomorphic
- IE9以降をサポートする場合はpolyfill を使用してください。なぜならIEだからです。
ルーティングの設定
route(callback)
URLが変更されると、与えられたcallback
を実行します。こんな感じです。
route(function(collection, id, action) {
})
もし、例えばURLがcustomers/987987/edit
に変わったとすると、上の例の引数は次のようになるでしょう。
collection = 'customers'
id = '987987'
action = 'edit'
URLは以下のような方法で変更することが可能です。
- 新しいハッシュがロケーションバーに入力されたとき
- 戻る/進むボタンが押されたとき
route(to)
が呼び出されたとき- アンカータグがクリックされたとき
route(filter, callback)
>= v2.3
URLが変更された際、filter
に一致すれば、与えられたcallback
を実行されます。
// `/fruit`のみに一致
route('/fruit', function(name) {
console.log('The list of fruits')
})
ワイルドカード(*
)をfilter
内に使用すると、それらを引数として渡すことができます:
// URLが`/fruit/apple`に変更した場合
// 'apple'を`name`として渡す
route('/fruit/*', function(name) {
console.log('The detail of ' + name)
})
// URLが`/blog/2015-09/01`に変更した場合
// '2015', '09' と '01' を渡す
route('/blog/*-*/*', function(year, month, date) {
console.log('The page of ' + year + '-' + month + '-' date)
})
/old
と/old/and/anything
の両方に一致させたいときは、..
と書きます:
route('/old..', function() {
console.log('The pages under /old was moved.')
})
これはURLが検索キーワードを含む場合に便利です。
// URLが`/search?keyword=Apple`に変更したとき一致する
route('/search..', function() {
var q = route.query()
console.log('Search keyword: ' + q.keyword)
})
// 以下のように書くこともできますが、
// `*`は英数字とアンダースコアのみに一致することに注意
route('/search?keyword=*', function(keyword) {
console.log('Search keyword: ' + keyword)
})
メモ: ワイルドカードは以下の正規表現に置き換えられます:
*
:([^/?#]+?)
..
:.*
route.create()
>= v2.3
新しいサブ・ルータを返します。例:
var subRoute = route.create()
subRoute('/fruit/apple', function() { /* */ })
詳しくはルーティング・グループとルーティングの優先度を参照してください。
ルータの使用
route(to[, title, shouldReplace])
ブラウザのURLを変更して、route(callback)
で登録されたすべてのリスナに通知します。例:
route('customers/267393/edit')
バージョン2.3
から、タイトルも指定できます。
route('customers/267393/edit', 'Editing customer page')
内部では…
shouldReplace
がなければ、history.pushState()
が使われます。shouldReplace
があれば、history.replaceState()
が使われます。
route.start()
URL変更の検知を開始します。
route.start()
>= v2.3
Riotはルータを自動的に起動しません。あなた自身で始めることを忘れないでください。これはまた、お気に入りのルータを選択できることを意味します。 (メモ: v2.3より前はRiotが自動的にルータを起動しました。動作が変更されました)
route.start(autoExec)
urlの変更を検知し、現在のurlでルーティングを実行します。
route.start(true)
これは以下を省略したものです。
route.start()
route.exec()
>= v2.3
Riotはルータを自動的に起動しません。あなた自身で始めることを忘れないでください。これはまた、お気に入りのルータを選択できることを意味します。 (メモ: v2.3より前はRiotが自動的にルータを起動しました。動作が変更されました)
route.stop()
全てのローティングを停止します。リスナーを削除し、全てのコールバックをクリアします。
route.stop()
この方法はroute.startと一緒に使用します。例:
route.stop() // 古いコールバックを解除
route.start() // 再起動
subRoute.stop()
>= v2.3
サブルータのルーティングのみ停止します。リスナーを削除し、全てのコールバックをクリアします。
var subRoute = route.create()
subRoute('/fruit/apple', function() { /* */ })
subRoute.stop()
route.exec()
現在のブラウザのパスを”定位置で”記憶し、変更を待つことなくルーティングを実行します。
route.exec(function(collection, id, action) {
})
注意: route.exec(callback)
はバージョン2.3
から非推奨となりました。
route.query()
>= v2.3
URLからクエリを抽出するときに有効な関数です。
// URLが`/search?keyword=Apple&limit=30`に変更されたとき
route('/search..', function() {
var q = route.query()
console.log('Search keyword: ' + q.keyword)
console.log('Search limit: ' + q.limit)
})
ルータをカスタマイズする
route.base(base)
ベースパスを変更します。以下のようなURLである場合:
http://riotexample.com/app/fruit/apple
ベースパスを/app
に変更すると、/fruit/apple
部分のみを気にすれば良くなります。
route.base('/app')
デフォルトのbase
の値は”#”です。ハッシュバンを使いたい場合は、#!
に変更してください。
route.base('#!')
Warning
ベースから#
を削除したとしても、webサーバーはURLが何であってもアプリケーションを実行する必要があります。なぜなら、ブラウザ上のアプリケーションがURLを操作しているからとなります。WebサーバーはURLの処理方法を知りません。
route.parser(parser[, secondParser])
デフォルトパーサーを独自のものに変更します。これはパスを解析する例です。
!/user/activation?token=xyz
route.parser(function(path) {
var raw = path.slice(2).split('?'),
uri = raw[0].split('/'),
qs = raw[1],
params = {}
if (qs) {
qs.split('&').forEach(function(v) {
var c = v.split('=')
params[c[0]] = c[1]
})
}
uri.push(params)
return uri
})
そして、これがURLが変更された場合に受け取るパラメータです。
route(function(target, action, params) {
/*
target = 'user'
action = 'activation'
params = { token: 'xyz' }
*/
})
二つめのパーサ
>= v2.3
secondParser
を指定すれば、二つめのパーサも変更できます。この二つめのパーサはURLフィルターで使われています:
// デフォルトの第二パーサ
function second(path, filter) {
var re = new RegExp('^' + filter.replace(/\*/g, '([^/?#]+?)').replace(/\.\./, '.*') + '$')
if (args = path.match(re)) return args.slice(1)
}
route.parser(first, second)
パーサが何も返さなかった場合は、次に一致するルートを探します。
ルーティング・グループ
サーバサイドの従来のルータは高度に一元管理されていますが、最近は画面のいたるところにルータが使われるようになってきています。以下の例をとってみましょう。
<first-tag>
<p>First tag</p>
<script>
route('/fruit/*', function(name) {
/* 共通するアクション */
})
</script>
</first-tag>
<second-tag>
<p>Second tag</p>
<script>
route('/fruit/apple', function(name) {
/* 特別なアクション */
})
</script>
</second-tag>
二つのタグにルーティングが指定されていますが、良さそうに見えますか?いいえ、これは動作しません。片方のルータのみが起動するため、次はどちらにルーティングするか判断がつきません。そこで、それぞれのタグ定義ごとに分離したルーティング・グループを作成する必要となります。例:
<first-tag>
<p>First tag</p>
<script>
var subRoute = route.create() // create another routing context
subRoute('/fruit/*', function(name) {
/* do something common */
})
</script>
</first-tag>
<second-tag>
<p>Second tag</p>
<script>
var subRoute = route.create() // create another routing context
subRoute('/fruit/apple', function(name) {
/* do something SPECIAL */
})
</script>
</second-tag>
ルーティングの優先度
ルータは最初に一致するルーティングを実行しようとします。したがって次の例では、routing-Bと-Cは呼ばれることがありません。
route('/fruit/*', function(name) { /* */ }) // routing-A (1)
route('/fruit/apple', function() { /* */ }) // routing-B (2)
route('/fruit/orange', function() { /* */ }) // routing-C (3)
これは正常に動作します。
route('/fruit/apple', function() { /* */ }) // routing-B (1)
route('/fruit/orange', function() { /* */ }) // routing-C (2)
route('/fruit/*', function(name) { /* */ }) // routing-A (3)
そしてフィルターを指定したルーティングは、フィルターなしのルーティングよりも優先されます。次の例では、routing-Xは最初に定義されていますが、最後に呼ばれます。
route(function() { /* */ }) // routing-X (3)
route('/fruit/*', function() { /* */ }) // routing-Y (1)
route('/sweet/*', function() { /* */ }) // routing-Z (2)
タグベースルーティング
>= v3.1
この機能はルートを宣言タグとして記述することを可能にします。
<app>
<router>
<route path="apple"><p>Apple</p></route>
<route path="banana"><p>Banana</p></route>
<route path="coffee"><p>Coffee</p></route>
</router>
</app>
この機能を使用するには、 route.js
の代わりに route+tag.js
を読み込む必要があります。
<script src="path/to/dist/route+tag.js"></script>
ES6の場合
import route from 'riot-route/lib/tag' // パスはcjsとは少し異なることに注意してください
CommonJSの場合
const route = require('riot-route/tag')
利用可能なタグ
<router>
- 複数のルートを含むことができます
const r = route.create()
と等価ですので、サブルータを作成します
<route>
path
属性を持っています<route path="fruit/apple">
はr('fruit/apple', () => { ... })
と等価です- ルートが選択されると、routeイベントがその子ノードでトリガーされ、引数が渡されます(詳細は下記参照)
ワイルドカード引数のキャプチャ
ルーティングでワイルドカード *
を使うことができることを忘れないでください。もちろん、tag-based routingでも同様のことができます。
<app>
<router>
<route path="fruit/apple"><p>Apple</p></route>
<route path="fruit/*"><inner-tag /></route>
</router>
</app>
<inner-tag>
<p>{ name } is not found</p>
<script>
this.on('route', name => this.name = name)
</script>
</inner-tag>
上記の例を見てください。 fruit/pineapple
を取得したとき、route
イベントが<inner-tag>
で発火し、 'pineapple'
という引数が一つ渡されます。
実際の例
通常は、ルーティング処理中に外部APIを呼び出してデータを取得します。このような目的のために route
イベントをフックするのは上手いやり方です。 例えば:
<app>
<router>
<route path="user/*"><app-user /></route>
</router>
</app>
<app-user>
<p>{ message }!</p>
<script>
this.on('route', id => {
this.message = 'now loading...'
getUserById(id).then(user => {
this.update({
message: `Hello ${ user.name }!`
})
})
})
</script>
</app-user>
いくつかの注意点
- ルータは最初の
<router>
タグがマウントされた後で自動的に起動します。自分でrouter.start(true
を呼び出す必要はありません。 - ルーティングの
base
を変更するには、route.base('/path/to/base/')
を使用してください。