迷你MVVM框架 avalonjs 学习教程20、路由系统
SPA的成功离开不这三个东西,分层架构,路由系统,储存系统。分层架构是我们组织复杂代码的关键,这里特指MVVM的avalon;路由系统是将多个页面压缩在一个页面的关键;储存系统特指本地储存,是安全保存大量数据的关键。本章节介绍的是avalon三柱臣之一的mmRouter(内含mmHistory)。
我们先上一个示例吧。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>路由系统</title> <script src="avalon.js"></script> <script> require(["mmRouter"], function() { var model = avalon.define({ $id: "test", currPath: "", params: {}, query: {}, args: "[]" }) function callback() { model.currPath = this.path var params = this.params if ("time" in params) { params.time = avalon.filters.date(params.time, "yyyy年M月dd日") } model.params = params model.args = "[" + [].slice.call(arguments).join(",") + "]" model.query = this.query } avalon.router.get("/aaa/", callback) avalon.router.get("/bbb", callback) avalon.router.get("/ccc/:ccc", callback) avalon.router.get("/ddd/{time:date}/", callback) avalon.router.get("/eee/{count:\\d{4}}/", callback) avalon.router.get("/fff", callback) avalon.history.start({ basepath: "/avalon" }) avalon.scan() }) </script> </head> <body > <div ms-controller="test"> <table width="100%" height="300"> <tr> <td width="250"> <ul> <li><a href="#!/aaa">aaa</a></li> <li><a href="#!/bbb?uu=3445345&were=4324">bbb</a></li> <li><a href="#!/ccc/etretr">ccc</a></li> <li><a href="#!/ddd/2014-09-19">ddd</a></li> <li><a href="#!/eee/2222">eee</a></li> <li><a href="#!/fff?a=1&nn=4&dfg=676">fff</a></li> </ul> </td> <td> <div style="color:red">this.path: {{currPath}}</div> <div style="color:blue">arguments: {{args}}</div> <fieldset> <legend>this.params</legend> <ol> <li ms-repeat="params"> {{$key}}: {{$val}}</li> </ol> </fieldset> <fieldset> <legend>this.query</legend> <ol> <li ms-repeat="query"> {{$key}}: {{$val}}</li> </ol> </fieldset> </td> </tr> </table> <div style="height: 600px;width:1px;"> </div> <p id="eee">会定位到这里</p> </div> </body> </html>
眼见为实,可以看到mmRouter很好地在IE6下运行,对于高版本的浏览器更不在话下。回退按钮,hash如果与页面上的某个锚记同名,它也会自动定拉到那里去。
我们接着详细介绍一下它的用法吧。
- 先引入mmRouter(请将mmRouter.js、mmHistory.js这两个文件与avalon.js放在一起)
- 定义VM
- 定义路由规则
- 启动历史管理器
- 开始扫描
mmHistory是用于历史管理,它会劫持页面上所有点击链接的行为,当这些链接是以#/ 、#!/开头,就尝试匹配路由规则,阻止页面刷新(通过hash方式或HTML5的replaceState方式)。mmRouter是给我们定义路由规则,路由规则可以更精细地指定每个参数(param)的匹配规则,如果符合就执行对应的回调,如果不符合,就进入error回调。
当用户点击页面链接时,路址栏会发生变化,avalon是参考了angular的方式来处理路址栏。
- Hashbang模式(默认), 这个模式下所有浏览器都支持
- HTML5模式, 这个只能应用于firefox, chrome, safari,IE10+,如果浏览器不支持此特性,即使你设置avalon.history.start({html5Mode:true}),它也是在Hashbang模式下运行。
当我们使用history API mode的时候,我们对于不同的浏览器,需要不同的链接, 但我们只需要提供其hash部分就行了,例如<a href=“#/aaa”>link</a>
当用户单击这个超链接时:
- 在Hashbang mode中,URL会改为/index.html#!/aaa
- 在HTML5 mode中,URL会改为/index.html/aaa
你会发现,Hashbang mode中的#!后面的部分等于HTML5 mode中的除域名外的所有部分。而#!其实是google的 _escaped_fragment_ 爬抓规则, 方便我们的AJAX应用也能被爬虫收录。当然,我们还需要在head标签内添加一个meta标签:
<meta name="fragment" content="!" />
想了解更多关于这个技术的信息,可以查看这里 下面是路由器的API列表:
- avalon.history.start(opts), 开始监听URL变化,opts。
- avalon.history.stop(), 中止监听URL变化。
- avalon.router.get(path, callback),用于添加路由规则。第一个为路由规则,如"/aaa", "/bbb/:bbbId","/eee/{eeeId}/ddd/{dddId:[0-9]{6}}" 冒号后的东西或花括号的东西表示为参数,花括号模式下还可以指定匹配规则。callback为回调函数,框架会将冒号后的或花括中的匹配内容传进来,此外this对象,包含了path、 params、 query等对象与属性。
- avalon.router.add(method, path, callback) , 添加回调,第一个为请求类型,如GET,POST,DELETE什么, 第2个为路由规则,第3个为回调函数
- avalon.router.error(callback),如果没有一条路由规则满足此请求,那么就转交此回调处理,我们可以在里面写跳转到404页面这样的逻辑
- avalon.router.navigate(path),强制触发对应路径的回调
- avalon.router.setLastPath(path) , 这是框架自己调用,保存最近一次跳转的路径
- *avalon.router.getLastPath() *,取得最近一次跳转的路径,比如用户F5强制页面,你在ready回调中执行此方法,得到path,然后将它放进navigate中就能回到原来的页面了。
最后我们再详细介绍一下路由规则,它是一个字符串,必须以/开头,它可以在每个/后直接接冒号,表示这之后到下一个斜线或末尾是一个参数。它也可以使用花括号表示里面的内容也一个参数。斜钱风格是来自backbone与express的,花括号风格是来自ui-router。花括号风格更为强大些,因此它可以指定更详细的匹配规则。
avalon.router.get("/ddd/:dddID/",callback) avalon.router.get("/ddd/{dddID}/",callback) avalon.router.get("/ddd/{dddID:[0-9]{4}}/",callback) avalon.router.get("/ddd/{dddID:int}/",callback)
mmRouter默认有四个参数匹配器,分别叫做date, init, bool, string。
$types: { date: { pattern: "[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])", decode: function(val) { return new Date(val.replace(/\-/g,"/")) } }, string: { pattern: "[^\/]*" }, bool: { decode: function(val) { return parseInt(val, 10) === 0 ? false : true; }, pattern: "0|1" }, int: { decode: function(val) { return parseInt(val, 10); }, pattern: "\d+" } }
decode方法是用来将我们从地址栏抽取出来的那些小字符串转换为想要的类型或格式。如果你想要更多效果,还可以自己添加,如avalon.router.$type.d4 = { pattern: ’[0-9]{4}’, decode: Number}
avalon.router.get("/ddd/{dddID:d4}/",callback)
总而言之,mmRouter是远远比angular自带路由器与backbone那个强大N多。未来还会进一步开发类似ui-router那样基于状态管理的路由器,敬请期待!