回顾
如果有小伙伴想要回顾之前的知识的话,点击下面的链接查看之前所有的源码分析流程👇
《Vue-Router源码解析:目录》
hashRouter
VueRouter有两种模式可以选择(其实有3种,哈哈),hash
和history
。其实两者的实现原理都是差不多的,我们这里挑hash
来讲解吧。
在不使用路由系统的网页中,如果要跳转就必须要改变url并且刷新页面。但是我们在使用路由系统的网页中(React-Router-Dom也是一样),不难发现,虽然url变化了,但是页面却没有刷新,这是怎么做到的?
在html5中有一个对象history
,它有三个方法,history.pushState()
,history.back()
,history.go()
,这三个方法都可以做到改变URL且不刷新页面。那VueRouter是怎么知道url被改变了呢?相对应的还有两个监听器popstate
、hashchange
,可以来监听url的变化。其中hash router
就是使用了hashchange
这个监听器。
我们先来看看hash router
的构造器
constructor
constructor (router: Router, base: ?string, fallback: boolean) {
super(router, base)
// check history fallback deeplinking
if (fallback && checkFallback(this.base)) {
return
}
ensureSlash()
}
这里使用了一个ensureSlash
方法,进入。
ensureSlash
function ensureSlash (): boolean {
const path = getHash()
if (path.charAt(0) === '/') {
return true
}
replaceHash('/' + path)
return false
}
首先使用getHash
获取一个path
,进入getHash
。
getHash
export function getHash (): string {
// We can't use window.location.hash here because it's not
// consistent across browsers - Firefox will pre-decode it!
let href = window.location.href
const index = href.indexOf('#')
// empty path
if (index < 0) return ''
href = href.slice(index + 1)
return href
}
由于火狐浏览器一些兼容性的问题,不能直接使用window.location.hash
来获取hash
部分的path
,这里VueRouter使用了原生的字符串截取的方法获取hash
部分的path
。我们以一开始输入浏览器的地址为:https://www.abc.com/
为例,这里getHash
后的值是:''
,一个空字符串。
回到ensureSlash
:
function ensureSlash (): boolean {
const path = getHash()
if (path.charAt(0) === '/') {
return true
}
replaceHash('/' + path)
return false
}
由于path
是空字符串,所以这边直接调用了replaceHash
,进入。
replaceHash
function replaceHash (path) {
if (supportsPushState) {
replaceState(getUrl(path))
} else {
window.location.replace(getUrl(path))
}
}
一开始先检测浏览器对pushState
api的兼容情况,如果兼容的话就会调用getUrl
传入‘/’
并调用replaceState
将getUrl
的结果传入replaceState
的参数中。
先来看看getUrl
。
getUrl
function getUrl (path) {
const href = window.location.href
const i = href.indexOf('#')
const base = i >= 0 ? href.slice(0, i) : href
return `${base}#${path}`
}
这边的代码很简单,就是在https://www.abc.com/
后边加上了#/
,所以最后的结果是https://www.abc.com/#/
。
我们再来看看replaceState
。
replaceState
export function replaceState (url?: string) {
pushState(url, true)
}
其实调用的是pushState
只不过第二个参数传入的为true
。
pushState
export function pushState (url?: string, replace?: boolean) {
saveScrollPosition()
// try...catch the pushState call to get around Safari
// DOM Exception 18 where it limits to 100 pushState calls
const history = window.history
try {
if (replace) {
// preserve existing history state as it could be overriden by the user
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)
}
}
由于参数replace
传入的事true
,所以最后调用的是window.history.replaceState
这个API,这个API会改变当前URL,但是不会产生一条历史记录,也就是说你按浏览器的回退键是不能返回上一个页面的。
所以在hashRouter
的构造器中的ensureSlash
就是在你访问页面时如果没有带上#
就自动帮你带上#
。
我们再来看看一个平时用的最多的API,push
,看看hashRouter
是怎么实现push
的。
push
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(
location,
route => {
pushHash(route.fullPath)
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
},
onAbort
)
}
假设此时用户是这么调用的this.$router.push("/foo")
,所以此时的location
是/foo
。
此时VueRouter会调用this.transitionTo
去开始执行一些钩子函数,然后开始resolve
组件,在完成这些之后会调用pushHash(route.fullPath)
这个方法去改变url,其实pushHash
最终调用的就是pushState
,只不过replace
传入的是false
。
总结
其实hashRouter
的实现还是比较简单的,说白了就是HTML5的history
对象,以及hashchange
监听器。当然了,historyRouter
实现也差不多,总体来说还是比较简单的。