Appearance
前端路由
元信息
- 目标:掌握"URL 变了、页面没刷新"的原理——路由即状态;能用 hash 与 History API 两种机制实现可收藏、可分享的客户端路由,并知晓 history 模式的服务端改造
- 关键概念:路由、路由表、静态路由、动态路由、hash、onhashchange、History API、pushState、popstate
- 关联阶段:cp3
- 常见误区:以为 URL 一变就会触发服务端请求(hash 与 pushState 改动不会刷新页面);以为链接只写 onclick 不写 href 也行(丢失可收藏与分享能力);以为 history 模式无需服务端配合(直接访问深链会命中服务端路由,须统一重定向回首页)
什么是路由 route?
指符号到实体、地址到实物之间的对应或映射关系,典型例子如ip地址到主机的映射
- route: 路由项,即一个映射规则
- router:
- 路由表:作为一组相关路由项的集合容器
- 路由器:管理路由项、执行路由转换的设备
服务端路由
由服务端定义,将URL映射到服务端资源的路由,即当用户在浏览器中访问url时,将触发一个服务端请求和获得一个服务端响应,一般有两种定义方式:
- Web服务器(如Nginx)定义:一般是页面或文件等静态资源的简单映射,即静态路由
- http://x.com/posts/1.html -> server:/www/posts/1.html
- 服务端脚本定义:如php\jsp\asp等,功能更灵活强大,可实现动态路由,可对应于访问服务器上的任何资源,如文件、数据库、进程等
- http://x.com/posts/1 -> server:/www/blog/posts.php?id=1
- http://x.com/posts/1 -> server:/www/blog/index.php?q=posts/1
静态路由与动态路由
- 静态路由:一对一映射关系,代表服务器端的静态内容或资源,形如
x.com/posts/1.html - 动态路由:一对多映射关系,通过查询参数等代表服务器端的动态内容或资源, 形如
x.com/posts/show.php?id=[id]
客户端路由:隐式定义
上述的两个a链接,相当于两个客户端路由,当它们被触发时,不会触发服务端请求,而是触发客户端的脚本
- 文本为
index的<a>元素 =》一个内联匿名函数 - 文本为
show的<a>元素 =》一个show()函数,该函数接受一个动态的id参数
隐式定义问题是:一改变了a元素的语义和功能;二无法被分享和收藏
客户端路由:显式声明
- 给每一个动态页面或客户端函数显式命名或编址,当用户访问该地址时,即渲染对应的动态页面或调用对应的客户端函数
- 有了地址,就可以支持浏览器收藏、上一步、下一步等Web传统交互方式
- 可收藏:意味着可生成分享的链接,可以实现互联、可以恢复到应用的任一状态,这是Web应用相比于桌面应用的最大优势
- 以路由为中心,利于代码的组织,如模块化、可重用等
客户端路由的实现机制:基于Hash
以http://x.com/index.html为例,浏览器的行为是:
- 如果该url的路径(path)部分发生变化,譬如url改变为:
http://x.com/index.html?key=sport,则浏览器将视其为一个新的网络地址,即向服务端发起新的http请求,这意味着刷新当前的页面- 由于发起了新的http请求,需要由服务端来处理,故这类路由一般默认为服务端路由
- 但如果改变的是锚点
#之后内容,譬如上述url改变为http://x.com/index.html#posts/show/32,因为#的语义是页内寻址,浏览器视为保持当前页面不变,即不会向服务端发送新的请求从而导致页面被刷新- 由于保持当前页面不变,仍由客户端来处理,则可以利用hash的这一特性来实现客户端路由
基于hash的实现
锚点#之后的内容称之为hash,浏览器提供onhashchange事件以监听hash的变化,则只需提供事件处理函数如下:
基于hash的路由实例
使用示例
客户端路由的实现机制:基于History API
在 HTML5 规范中,提供了增强的 History API , 无需#的后缀,而直接将URL改造为前端路由
- history.pushState(state,title,url); // 添加新的状态到历史状态栈
- history.replaceState(state,title,url); // 用新的状态代替当前状态
开发者使用上述API,管理浏览器的浏览历史记录,并且地址栏里的URL变化不会导致浏览器触发服务端请求和页面刷新
- 浏览器的默认交互操作,如上一步、下一步、刷新等都会触发history state的出栈或入栈
- 提供 onpopstate 事件,监听history state的变化
详情参考 https://developer.mozilla.org/zh-CN/docs/Web/API/History
基于history的路由示例
路由实现技术比较
- hash 兼容低版本浏览器,且不会与服务端路由发生冲突,但URL稍显冗长
- history API 更符合URL格式,
- 开发人员需要显式地拦截所有的链接事件,将事件处理代码与history state进行关联
- 可能与服务端路由冲突,需要对服务端进行改造
history API模式下的路由冲突与改造
假设服务端路由为:
- x.com/ => index.html
- x.com/posts => posts/index.php
客户端路由为:
- x.com/posts => function postsIndex(){}
- 初始访问
/时,浏览器将访问服务端的index.html,该文档会加载客户端路由的相关js文件。此时,当点击/posts链接时,由于click事件被客户端路由拦截,它不会触发服务端请求 - 但如果将
/posts分享或收藏时,即直接在浏览器中访问/posts,则显然会触发服务端请求posts/index.php,这对于单页应用而言,可能是个错误 - 解决办法是,在服务端做改造,即拦截所有除
/之外的服务端路由,将其统一重定向到/
简单的使用办法是使用http-server-spa 包,它默认会支持上述解决办法,无需单独配置
前端路由库 director.js
https://github.com/flatiron/director 是一个简单的 client-side router库,支持基于hash技术的路由,方便处理对hash的监听和解析,使得代码更清晰、易用
详见 https://www.cnblogs.com/Showshare/p/director-chinese-tutorial.html
前端路由库 page.js
page.js 支持基于History api的客户端路由,并且提供了诸如重定向,页面刷新等实用功能
需要配合 http-server-spa 使用
路由库与例子
- 框架中立的route.js https://github.com/riot/route
- 编写路由的中文例子 https://github.com/biaochenxuying/route