vue-router 主要有三种模式 hash、history、abstract(服务端使用)
前端实现方式主要有以下两种:
hash
---- 利用 URL 中的 hash(“#”,window.location.hash)----(默认方式)- 利用
History interface
在 HTML5 中新增的方法 >>详情点击
// 根据 mode 确定 history 实际的类并实例化
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract':
this.history = new AbstractHistory(this, options.base)
break
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`)
}
}
HashHistory
主要替换路由的方法:HashHistory.push()
和 HashHistory.replace()
push
其中 push 是向栈中去添加一条记录
具体执行步骤:
// 1、调用方法
$router.push()
// 2、根据hash模式调用,设置hash并添加到浏览器历史记录(添加到栈顶)(window.location.hash= XXX)
HashHistory.push()
// 3、监测更新,更新则调用History.updateRoute()
History.transitionTo()
// 4、更新路由
History.updateRoute()
// 5、替换当前app路由
{app._route= route}
// 6、更新视图
vm.render()
hash虽然出现在url中,但不会被包括在http请求中,它是用来指导浏览器动作的,对服务器端完全无用,因此,改变hash不会重新加载页面。
可以为hash的改变添加监听事件:
window.addEventListener("hashchange",funcRef,false)
每一次改变 hash(window.location.hash)
,都会在浏览器访问历史中增加一个记录。
利用 hash 的以上特点,就可以来实现前端路由"更新视图但不重新请求页面"的功能了。
HashHistory.push()
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.transitionTo(location, route => {
pushHash(route.fullPath)
onComplete && onComplete(route)
}, onAbort)
}
function pushHash (path) {
window.location.hash = path
}
hash的改变会自动添加到浏览器的访问历史记录中。
那么视图的更新是怎么实现的呢,我们来看看父类 History 中的 transitionTo()
方法:
transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const route = this.router.match(location, this.current)
this.confirmTransition(route, () => {
this.updateRoute(route)
...
})
}
updateRoute (route: Route) {
this.cb && this.cb(route)
}
listen (cb: Function) {
this.cb = cb
}
可以看到,当路由变化时,调用了 Hitory 中的 this.cb
方法,而 this.cb
方法是通过 History.listen(cb)
进行设置的,回到 VueRouter
类定义中,找到了在 init()
中对其进行了设置:
init (app: any /* Vue component instance */) {
this.apps.push(app)
history.listen(route => {
this.apps.forEach((app) => {
app._route = route
})
})
}
app 为 Vue 组件实例,但是 Vue 作为渐进式的前端框架,本身的组件定义中应该是没有有关路由内置属性 _route
, 如果组件中要有这个属性,应该是在插件加载的地方,即 VueRouter
的 install()
方法中混入Vue对象的,install.js 的源码:
export function install (Vue) {
Vue.mixin({
beforeCreate () {
if (isDef(this.$options.router)) {
this._router = this.$options.router
this._router.init(this)
Vue.util.defineReactive(this, '_route', this._router.history.current)
}
registerInstance(this, this)
},
})
}
通过 Vue.mixin()
方法,全局注册一个混合,影响注册之后所有创建的每个Vue实例,该混合在 beforeCreate
钩子中通过 Vue.util.defineReactive()
定义了响应式的 _route
属性。所谓响应式属性,即当 _route
值改变时,会自动调用 Vue 实例的 render()
方法,更新视图。
replace
replace 是将栈顶的一条记录给替换掉
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.transitionTo(location, route => {
replaceHash(route.fullPath)
onComplete && onComplete(route)
}, onAbort)
}
function replaceHash (path) {
const i = window.location.href.indexOf('#')
window.location.replace(
window.location.href.slice(0, i >= 0 ? i : 0) + '#' + path
)
}
可以看出,它与 push()
的实现结构基本相似,不同点它不是直接对 window.location.hash
进行赋值,而是调用 window.location.replace
方法将路由进行替换。
HTML5History
-
History interface是浏览器历史记录栈提供的接口,通过back(), forward(), go()等方法,我们可以读取浏览器历史记录栈的信息,进行各种跳转操作。
-
从HTML5开始,History interface有进一步修炼:pushState(), replaceState() 这下不仅是读取了,还可以对浏览器历史记录栈进行修改