VueRouter
vue-router是Vue.js框架的路由插件。
“更新视图但不重新请求页面” 是前端路由原理的核心之一。目前浏览器环境中这一功能的实现主要有两种方式。
- 利用URL中的hash(“#”)
- 利用History interface在HTML5中新增的方法。
创建VueRouter的实例对象时,mode以构造函数参数的形式传入。
- 作为参数传入的字符串属性mode只是一个标记,用来指示实际起作用的对象属性history的实现类,两者的对象关系为:
history=>HTML5History,hash=>HashHistory,abstract=>AbstractHistory - 在初始化history之前,会对mode做一些校验:若浏览器不支持
HTML5Histor
y方式(通过supportsPushState
变量来判断),则mode强制为“hash
”;若不是在浏览器环境下运行,则mode强制为‘abstract
’ - VueRouter类中的
onReady()
,push()
等方法只是一个代理,实际是调用的具体history对象的对应方法,在init()
方法中初始化时,也是根据history对象具体的类别执行不同的操作
HashHistory
hash("#")符号的本来作用是加在URL中指示网页中的位置:
http://www.example.com/index.html#print
#符号本身以及它后面的字符称之为hash,可以通过window.location.hash属性读取。它具有如下特点:
- hash虽然出现在url中,但不会被包括在http请求中。它是用来指导浏览器动作的,对服务器端完全无用,因此,改变hash不会重新载入页面。
- 可以为hash的改变添加监听事件:
window.addEventListener("hashChange", funRef, false)
- 每一次改变hash(window.location.hash),都会在浏览器的访问记录中增加一个新的记录。
利用hash的以上特点,就可以在前端实现“只更新视图不重新请求页面”的功能了。
HashHistory.push()
transitionTo()
方法是父类中定义的用来处理路有变化基础逻辑的。
push()
的方法最主要的是对window
的hash
对象进行了直接赋值。
window.location.hash = route.fullPath
hash的改变会自动添加到浏览器访问的历史记录中。
那么视图是怎么改变的呢??
当路由变化时候,调用了history中的this.cb
方法,而this.cb
方法是通过History.listen(cb)
进行设置的。
回到VueRouter类定义中,在init()
方法中对其进行设置。
通过Vue.mixin()
方法,全局注册一个混合,影响注册之后每一个Vue实例,该混合在beforeCreated
钩子中通过Vue.util.defineReactive()
定义了响应式的_router
属性。所谓响应式属性,即当_router值改变时,会自动调用Vue实例的render()
方法,更新视图。
总结一下,从设置路由改变到更新视图的流程如下:
1. $router.push() //调用方法
2. HashHistory.push() //根据hash模式调用,设置hash并添加到浏览器历史记录中(添加到栈顶)(window.location.hash = XXX)
3. History.transitionTo() //监测更新
4. History.updateRoute() // 更新路由
5. {app._route = route} // 替换当前app路由
6. vm.render() // 更新视图
HashHistory.replace()
replace()
方法与push()
方法的不同之处在于,它不是将新路由添加到浏览器访问历史的栈顶,而是替换掉当前的路由。
它与push()
的实现结构基本相似,不同点在于它不是直接对window.location.hash
进行赋值,而是调用window.location.replace
方法将路由进行替换。
监听地址栏
用户还可以在浏览器地址栏中输入改变路由,所以VueRouter
还需要监听地址栏中路由变化。在HashHistory
中这一功能通过setListeners
实现。
该方法设置监听了浏览器事件hashchange
,调用的函数为replaceHash()
,即在浏览器地址中直接输入路由相当于代码调用了replace()
方法。
HTML5History:
History interface是浏览器历史记录栈提供的接口,通过back()
,forword()
,go()
等方法,我们可以读取浏览器历史记录栈的信息,进行各种跳转工作。
HTML5开始,History interface提供了两个新的方法:pushState()
, replaceState()
使得我们可以对浏览器历史记录栈进行修改:
window.history.pushState(stateObject, title, URL);
window.history.replaceState(stateObject, title, URL);
- stateObject:当浏览器跳转到新的状态时,将触发popState事件,该事件携带这个参数的副本
- title:所添加记录的标题
- URL:所添加记录的URL
这两个方法有个共同特点:当调用他们修改浏览器历史记录栈后,虽然当前URL改变了,但浏览器不会立即发送请求该URL。这就为单页面应用前端“更新视图但不重新请求页面”提供了基础。
代码结构中更新视图的逻辑与hash基本类似,只不过将对window.location.hash
直接进行赋值window.location.replace()
替换 变为调用 window.history.pushState()
和window.history.replaceState()
。
在HTML5History中添加对修改浏览器地址栏URL的监听是直接在构造函数中执行的。
两种模式比较:
pushState()
设置新的URL
可以是与当前URL
同源的任意URL
;而hash
只可修改#后面的部分,故只可设置与当前同文档的URL。pushState
设置新的URL
可以与当前URL
一模一样,这样也会把记录添加到栈中。而hash
设置的新值必须与原来不一样才会触发记录添加到栈中。pushState
通过stateObject
可以添加任意类型的数据到记录中,而hash
只可以添加短字符串。pushState
可以额外设置title
属性供后续使用。- 如果用户直接在地址栏中输入并回车,浏览器重启重新加载应用。
hash
模式仅改变hash
部分的内容,而hash
部分是不会包含在http
请求中的,所以hash
模式下遇到更具URL
请求页面的情况不会有问题。
但是history
模式会将URL
修改的就和正常请求后端的URL
一样,重新向后端发起请求,如果后端没有对应路由处理,就会返回404错误。 解决办法: 在服务端增加一个覆盖所有情况的候选资源, - 如果想在文件系统中直接加载Vue单页应用而不借助后端服务器,除了打包后一些路径设置外,还需要确保
Vue-router
使用的使hash
模式。