API · 路由器
Riot 路由器是一个最小化的路由器实现,实现了以下技术:
- pushState 和 history api
- 多个路由组
- 可替换的解析器
- 同构
- 使用 polyfill 可兼容IE 9及以下版本
配置路由
riot.route(callback)
当URL变化时执行 callback
例如
riot.route(function(collection, id, action) {
})
举个例子,如果 url 变成 customers/987987/edit
则上例中的参数是:
collection = 'customers'
id = '987987'
action = 'edit'
url 发生变化可能是以下几种情况:
- 地址栏中输入的新的 hash 值
- 当浏览器的 后退/前进按钮被点击
- 当代码中调用了
riot.route(to)
- 用户点击了锚点标签 (a 标签)
riot.route(filter, callback)
>= v2.3
当 url 发生变化且新的 url 匹配 filter
时执行 callback
. 例如:
// 精确匹配 `/fruit`
riot.route('/fruit', function(name) {
console.log('The list of fruits')
})
filter
支持 *
通配符,并且可以将其捕捉成参数:
// 如果 url 变成 `/fruit/apple`,
// 'apple' 将被捕捉成 `name` 参数
riot.route('/fruit/*', function(name) {
console.log('The detail of ' + name)
})
// 如果 url 变成 `/blog/2015-09/01`,
// 回调的参数将被捕捉成 '2015', '09' 和 '01'
riot.route('/blog/*-*/*', function(year, month, date) {
console.log('The page of ' + year + '-' + month + '-' date)
})
如果希望匹配url /old
和 /old/and/anything
, 可以写成 ..
:
riot.route('/old..', function() {
console.log('The pages under /old was moved.')
})
这种写法在 url 中包含搜索参数时很有用。
// 如果 url 变成 `/search?keyword=Apple` 将会匹配
riot.route('/search..', function() {
var q = riot.route.query()
console.log('Search keyword: ' + q.keyword)
})
// 可以象下面这么写,
// 但要注意 `*` 只能匹配字母,数字和下划线
riot.route('/search?keyword=*', function(keyword) {
console.log('Search keyword: ' + keyword)
})
注意: 在内部实现中,通配符被转换成下面这样的正则:
*
:([^/?#]+?)
..
:.*
riot.route.create()
>= v2.3
返回一个新的路由上下文。例如:
var subRoute = riot.route.create()
subRoute('/fruit/apple', function() { /* */ })
使用路由器
riot.route(to[, title, shouldReplace])
修改浏览器 url 并通知所有用 riot.route(callback)
注册的监听者。例如:
riot.route('customers/267393/edit')
从 v2.3 开始, 还可以设置 title:
riot.route('customers/267393/edit', 'Editing customer page')
可以利用第3个参数来替换当前的历史。当应用需要重定向到另一个页面时很有用。
riot.route('not-found', 'Not found', true)
在内部实现中…
- 如果没有
shouldReplace
, 将使用history.pushState()
. - 如果有
shouldReplace
, 将使用history.replaceState()
.
riot.route.start()
开始监听url的变化.
riot.route.start()
>= v2.3
Riot不会自动 start
路由器。不要忘记手动启动它。 这也意味着你可以选择你喜欢的路由器。
(注意:在 v2.3 之前的版本中,Riot会自动启动路由器。这个行为发生了变化)
riot.route.start(autoExec)
开始监听 url 变化,同时对当前 url 执行路由。
riot.route.start(true)
这只是对下面写法的简写形式:
riot.route.start()
riot.route.exec()
>= v2.3
Riot 不会自动 start
路由器. 不要忘了手动启动它。这也意味着你可以选择自己喜欢的路由器实现
(注意:在 v2.3 之前 Riot 会自动启动路由器. )
riot.route.stop()
停止所有的路由器。清除所有 监听器和回调.
riot.route.stop()
通常这个方法会与 riot.route.start 联合使用。例如:
riot.route.stop() // clear all the old router callbacks
riot.route.start() // start again
subRoute.stop()
>= v2.3
仅停止subRoute的路由。将会删除监听器和回调函数。
var subRoute = riot.route.create()
subRoute('/fruit/apple', function() { /* */ })
subRoute.stop()
riot.route.exec()
针对当前的浏览器url立即执行路由,而不是等url发生变化。例如:
riot.route(function() { /* 定义路由 */ })
riot.route.exec()
警告: riot.route.exec(callback)
从 v2.3
开始废弃.
riot.route.query()
>= v2.3
这是个辅助函数,用来从url中提取query参数。 例如:
// 如果 url 变成 `/search?keyword=Apple&limit=30` 将会匹配
riot.route('/search..', function() {
var q = riot.route.query()
console.log('Search keyword: ' + q.keyword)
console.log('Search limit: ' + q.limit)
})
自定义路由器
riot.route.base(base)
修改基础路径(base)。 如果 url 长成这样:
http://riotexample.com/app/fruit/apple
可以将基础路径设置为 /app
, 这样就只需要关注 /fruit/apple
.
riot.route.base('/app')
默认的 base
值是 “#”. 如果喜欢 hashbang, 可以改成 #!
.
riot.route.base('#!')
riot.route.parser(parser[, secondParser])
将默认解析器改成自定义解析器. 下面是自定义路径解析的例子:
!/user/activation?token=xyz
riot.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变化时,你可以获取这些参数:
riot.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)
}
riot.route.parser(first, second)
如果解析器没有返回值,会尝试匹配下一个路由规则。
路由分组
传统的服务端路由是高度集中的,但近来的趋势是在页面上到处使用路由。考虑下面的情况:
<first-tag>
<p>First tag</p>
<script>
riot.route('/fruit/*', function(name) {
/* 公用的部分 */
})
</script>
</first-tag>
<second-tag>
<p>Second tag</p>
<script>
riot.route('/fruit/apple', function(name) {
/* 个别的部分 */
})
</script>
</second-tag>
两个标签有各自的路由,看起来没什么问题,但事实上它无法工作。因为只有一个路由会被触发,我们也不知道是哪一个。所以,我们必须为每个标签定义创建单独的路由分组。例如:
<first-tag>
<p>First tag</p>
<script>
var subRoute = riot.route.create() // 创建新的路由上下文
subRoute('/fruit/*', function(name) {
/* 公用的部分 */
})
</script>
</first-tag>
<second-tag>
<p>Second tag</p>
<script>
var subRoute = riot.route.create() // 创建新的路由上下文
subRoute('/fruit/apple', function(name) {
/* 个别的部分 */
})
</script>
</second-tag>
路由优先级
这个路由器会从第一个路由开始尝试匹配。所以在下面的鸽子中, 路由-B 和 -C 将永远不会被触发.
riot.route('/fruit/*', function(name) { /* */ }) // 路由-A (1)
riot.route('/fruit/apple', function() { /* */ }) // 路由-B (2)
riot.route('/fruit/orange', function() { /* */ }) // 路由-C (3)
这么写就好使了:
riot.route('/fruit/apple', function() { /* */ }) // 路由-B (1)
riot.route('/fruit/orange', function() { /* */ }) // 路由-C (2)
riot.route('/fruit/*', function(name) { /* */ }) // 路由-A (3)
所有 带有过滤器 的路由优先级高于 不带过滤器 的路由. 这意味着下例中路由-X 虽然定义在前,但最后才执行匹配:
riot.route(function() { /* */ }) // 路由-X (3)
riot.route('/fruit/*', function() { /* */ }) // 路由-Y (1)
riot.route('/sweet/*', function() { /* */ }) // 路由-Z (2)