前言
前两天,有一位小伙伴问我:
为什么说vue-router做到更新视图但不重新请求页面?我看路由更新都有请求数据啊。
针对这个问题,我从以下几点进行答复。
一、什么叫不重新请求页面
注意,这里说的是不重新请求页面,而不是说不会请求数据。请求数据是在你每个组件的生命周期中发起的。而重新请求页面,就相当于你直接点击了浏览器右上角的那个“重新加载此页”的按钮,而你在进行路由切换的时候,会发现浏览器的加载按钮并没有发生变化,所以是没有进行重新请求页面的。而至于它是如何做到不重新请求页面却更新了视图的呢。我们接着看。
二、vue-router的几种模式
如果有人问你,vue-router有几种模式?你可能会不假思索的回答说两种,那就是history和hash,因为这两个是常用到的。而vue-router还有第三种模式:abstract。接下来,我们一个一个来看一下这三种模式。
1、history
history模式在实现无刷新更新路由上主要用到了history提供的api:pushState,replaceState,以及 window 的 popstate 事件。看下mdn对这三个东东的介绍:
总的来说就是,通过pushState和replaceState来修改路由,页面不会重新加载。然后在浏览器导航发生变化的时候触发 popstate 事件。具体的实现代码可以看vue-router的源码,在src/history/html5.js 中
export class HTML5History extends History {
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
pushState(cleanPath(this.base + route.fullPath))
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
}
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
replaceState(cleanPath(this.base + route.fullPath))
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
}
ensureURL (push?: boolean) {
if (getLocation(this.base) !== this.current.fullPath) {
const current = cleanPath(this.base + this.current.fullPath)
push ? pushState(current) : replaceState(current)
}
}
getCurrentLocation (): string {
return getLocation(this.base)
}
}
export function pushState (url?: string, replace?: boolean) {
saveScrollPosition()
const history = window.history
try {
if (replace) {
const stateCopy = extend({}, history.state)
stateCopy.key = getStateKey()
history.replaceState(stateCopy, '', url)
} else {
history.pushState({ key: setStateKey(genStateKey()) }, '', url)
}
} catch (e) {
window.location[replace ? 'replace' : 'assign'](url)
}
}
export function replaceState (url?: string) {
pushState(url, true)
}
2、hash
这里要区分一下3.x版本之前的和之后的,两者的实现方式有所不同。
- 2.x
2.x版本的实现是通过hash实现的,也就是url中#后面那一段。hash虽然也在url中,但改变它却不会引起页面的重新加载。虽然不会引起页面的重载,但是浏览器也会记录它的每次改变。以下是源码的一角:
function pushHash (path) {
window.location.hash = path
}
function replaceHash (path) {
const href = window.location.href
const i = href.indexOf('#')
const base = i >= 0 ? href.slice(0, i) : href
window.location.replace(`${base}#${path}`)
}
- 3.x
而到了3.x版本,vue-router进行了一次改版。其中主要的一点就是hash也采用了history的方式实现,当然,这里并不是完全抛弃了之前的hash,而是在支持histroy的情况下使用history的方式,否则还是用回之前的。来看下代码:
function pushHash (path) {
if (supportsPushState) {
pushState(getUrl(path))
} else {
window.location.hash = path
}
}
function replaceHash (path) {
if (supportsPushState) {
replaceState(getUrl(path))
} else {
window.location.replace(getUrl(path))
}
}
3、abstract
abstract其实就是用一个数组stack来模拟路由栈,对路由的增加修改其实就是对这个数组的增加修改。具体的实现就不展开了。
结尾
关于那位小伙伴的疑问以及延伸出来的各个模式的实现原理,就扒拉到这里了。
|