# Hash模式
# 实例化
跟history模式一样,都是在new VueRouter的时候,通过判断传入的mode来判断最后初始化路由模式
this.history = new HashHistory(this, options.base, this.fallback)成功
上述代码可以看出,初始化的时候,会创建一个HashHistory实例,将this、设定的基准路径base以及是否为降级成为的hash模式传入,我们找到HashHistory的定义
// src/history/hash.js export class HashHistory extends History { constructor (router: Router, base: ?string, fallback: boolean) { super(router, base) // 如果是降级来的,则重新生成降级的路径 // 如 base 为 /user 当前路径为/user/admin 则重新生成路径为/user#/admin if (fallback && checkFallback(this.base)) { return } // 如果hash开头是/,则代表是hash模式的路由 // 如果不是,则需要切换成hash模式的路由 ensureSlash() } }成功
2
3
4
5
6
7
8
9
10
11
12
13
14
跟HTML5History一样,都是继承自History,并且在初始化的时候进行的调用,这里就不赘述History的实现了。
紧接着,进行判断,是否为降级来的hash模式,如果是降级来的,当前路由可能不包含#,将当前路由进行调整
如果是降级来的,当前路由也包含#或者不是降级来的,检查当前路径是否符合hash模式的标准,不符合的进行调整
这里值得一提的是,如果不符合hash模式的标准时,会调用replaceHash方法去切换
function replaceHash (path) { // 如果环境支持history,则使用replaceState跳转 if (supportsPushState) { replaceState(getUrl(path)) } else { // 不支持直接走replace方法 window.location.replace(getUrl(path)) } }成功
2
3
4
5
6
7
8
9
这里会判断当前环境是否支持window.history,如果支持,则用到replaceState, 否则使用window.location.replace,这两种方式跳转的区别如下:
location.replace用于在浏览器中替换当前页面的 URL,立即加载新的 URL,但不会在浏览器历史中生成新的记录,因此用户不能通过后退按钮返回上一个页面。history.replaceState用于修改当前浏览器历史记录的状态,不会触发页面的刷新或加载,允许无刷新更新页面状态,同时允许用户通过后退按钮返回上一个状态。
# 初始化
初始化时,hash模式跟history模式前面调用的方法都一样,都是继承History的方法,唯一不同的是setupListeners方法的实现
setupListeners () { // 检查是否已设置监听器,避免重复设置 if (this.listeners.length > 0) { return } // 当前 Vue Router 实例 const router = this.router // 期望的滚动行为 const expectScroll = router.options.scrollBehavior // 是否支持滚动行为 const supportsScroll = supportsPushState && expectScroll // 添加滚动行为的监听器,如果支持滚动行为 if (supportsScroll) { this.listeners.push(setupScroll()) } const handleRoutingEvent = () => { // 获取当前地址信息 const current = this.current // 如果当前路径不符合hash模式,则直接进行替换并取消后续操作 if (!ensureSlash()) { return } // 进行路由转换 this.transitionTo(getHash(), route => { // 如果支持滚动行为,则处理滚动 if (supportsScroll) { handleScroll(this.router, route, current, true) } // 不支持history,则使用location.replace跳转 if (!supportsPushState) { replaceHash(route.fullPath) } }) } // 支持history,使用history相关方法跳转,用popstate监听 // 不支持history,使用location.replace跳转,用hashchange监听 const eventType = supportsPushState ? 'popstate' : 'hashchange' window.addEventListener( eventType, handleRoutingEvent ) this.listeners.push(() => { window.removeEventListener(eventType, handleRoutingEvent) }) }成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
首先,这个方法检查是否已经设置了监听器,如果已经设置,则避免重复设置,直接返回,以防止重复注册事件监听器。
获取当前 Vue Router 实例
router和期望的滚动行为expectScroll,这是在 Vue Router 的配置选项中定义的,用于控制路由切换时的滚动行为。确定是否支持滚动行为,并将结果存储在
supportsScroll变量中。supportsPushState是之前用来检查浏览器是否支持pushState的变量(在上一个问题中提到的),expectScroll则检查是否设置了滚动行为。如果支持滚动行为,调用
setupScroll()方法,并将其返回的滚动处理函数添加到this.listeners数组中。setupScroll()方法的目的是设置滚动行为的监听器,以在路由切换时执行滚动。创建
handleRoutingEvent函数,用于处理路由变化事件。当路由发生变化时,它将被调用。ensureSlash()方法检查当前路径是否符合 hash 模式,如果不符合,则进行替换并取消后续操作。ensureSlash()的目的是确保当前页面的 URL 使用 hash 模式。执行路由转换,调用
this.transitionTo()方法来实际处理路由的切换。在路由转换后,如果支持滚动行为,将调用handleScroll()方法来处理页面滚动。如果不支持
pushState,则使用location.replace方法进行跳转,而不是pushState方法。这里使用replaceHash()方法来完成对 URL 的替换。最后,根据是否支持
pushState,确定使用popstate事件或hashchange事件,并向window对象添加相应的事件监听器。然后,将处理路由事件的函数handleRoutingEvent添加到this.listeners数组中,以便后续可以移除这个监听器。这里通过window.removeEventListener来确保在不再需要监听器时将其移除。
# 常见方法
分析完实例化和初始化后,我们分析下我们工作中一些常见的方法
# push
push在VueRouter类中的的实现跟history模式是一致的,这里只分析不同的this.history.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 ) }成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这里跟history模式不一样的地方在于pushHash的调用,对不支持history环境做了一层兼容
function pushHash (path) { // 如果环境支持history,则使用pushState跳转 if (supportsPushState) { pushState(getUrl(path)) } else { // 直接修改hash window.location.hash = path } }成功
2
3
4
5
6
7
8
9
# replace
同理,replace的方法不同点也是在于this.history.replace的实现
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) { const { current: fromRoute } = this this.transitionTo( location, route => { // 新的路由地址直接替换当前的浏览器历史记录 replaceHash(route.fullPath) // 处理滚动相关 handleScroll(this.router, route, fromRoute, false) // 执行成功回调 onComplete && onComplete(route) }, onAbort ) }成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
也是同样的replaceHash方法,上面分析过,不再赘述
# go
与history模式相同,不赘述
# back
与history模式相同,不赘述
# 总结
hash模式的整体实现跟history模式类似,不同的是,为了更好的用户体验,hash模式针对环境的不同,采用了不同的方式
- 支持
history的环境中,与history模式一致 - 不支持
history的环境中,通过location.hash和location.replace等方法改变页面的url。同时监听hashchange方法,当使用浏览器前进后退按钮发生变更时,切换为对应的组件,从而实现整个页面路由的切换
← History模式 Abstract模式 →