4. createWebHistory()
1. 整体情况
该方法创建一个 HTML5 history,这是 SPA(Single Page Application, 单页应用程序)的常见 history。
1.1 方法源码
// 文件 history/html5.ts
export function createWebHistory(base?: string): RouterHistory {
// 规范化 base,兼容 <base> 标签,删除值尾部的 /
base = normalizeBase(base)
// 返回一个包含 location, state 属性和 push, replace 方法的对象
const historyNavigation = useHistoryStateNavigation(base)
// 返回一个包含 pauseListeners, listen, destroy 方法的对象
const historyListeners = useHistoryListeners(
base,
historyNavigation.state,
historyNavigation.location,
historyNavigation.replace
)
function go(delta: number, triggerListeners = true) {
if (!triggerListeners) historyListeners.pauseListeners()
history.go(delta) // 原生方法 window.history.go()
}
// 赋值
const routerHistory: RouterHistory = assign(
{
location: '',
base,
go,
createHref: createHref.bind(null, base), // 移除 hash 前的所有字符
},
historyNavigation,
historyListeners
)
// 两个 getter
Object.defineProperty(routerHistory, 'location', {
enumerable: true,
get: () => historyNavigation.location.value,
})
Object.defineProperty(routerHistory, 'state', {
enumerable: true,
get: () => historyNavigation.state.value,
})
return routerHistory
}
1.2 属性来源
根据 createWebHistory() 方法的代码,我们可以知道其返回的 RouterHistory 实例的各个属性的来源。如下:
base规范化gocreateHref
createWebHistory()
locationstatepush()replace()
useHistoryStateNavigation()
listendestroypauseListeners
useHistoryListeners()
1.3 逻辑分析
createWebHistory() 方法的底层是依赖 Web History API,基于对 Web History API 的理解,关于该方法,需要重点关注以下逻辑:
对
popstate事件的处理,以及如何和组件挂钩的对 Router History 栈的维护,因为它必然不能直接用原生的 session history stack
与 Router Matcher 的配合,因为理论上是先匹配找到下标,然后再用原生的
history.go(num)方法实现路由跳转的
2. 监听 popstate 事件
popstate 事件经过搜索发现,和 popstate 事件相关的逻辑都在 useHistoryListeners() 里。
2.1 useHistoryListeners()
useHistoryListeners()简化后的代码,如下:
2.2 私有方法 popStateHandler
popStateHandler预期是在这里完成的前端视图切换。
在这个事件监听器函数中,和切换前端视图相关的逻辑就一个,那就是:执行了 listeners 里的回调函数。
其中,listeners 里的回调函数是通过公有方法 listen() 动态 push 的。
接下来我们重点看下 listeners 里的回调函数是什么样子的,以及它们是什么时候存进来的。
2.3 调用方法 listen()
listen()useHistoryListeners() 暴露出去的公有方法 listen(),最终是通过 createWebHistory() 方法暴露出去的,即 RouterHistory 实例的 listen() 方法。
搜索 routerHistory.listen,结果如下:
也就是说在 createRouter() 时就 push 好了 listeners。接下来,看看它是如何使用参数 delta 以及如何切换前端视图的。
在 routerHistory.listen() 中,和 info.delta 相关的逻辑大部分是在 navigate() 的异常处理里,故暂且忽略。我们重点关注对参数 to 的处理,与之相关的代码如下:
至此,在响应 popstate 事件时执行的监听回调中,切换前端视图的任务主要由这两个方法完成:
pushWithRedirect()重定向导航navigate()普通导航
对于这两个方法的介绍,详见 createRouter()。
3. 维护 Router History 栈
经过搜索发现,和 history.pushState(), history.replaceState() 方法相关的逻辑都在 useHistoryStateNavigation() 里。
3.1 useHistoryStateNavigation()
useHistoryStateNavigation()简化后的代码,如下:
4. 路由跳转
经过搜索发现,和 history.back(), history.forward(), history.go() 方法相关的逻辑入口都在 RouterHistory 接口的定义里。
公共逻辑
处理 base 和 href
useHistoryStateNavigation(base)
useHistoryStateNavigation(base)该方法根据 base 返回一个包含 location, state 属性和 push, replace 方法的对象。
与 location 属性相关的逻辑,如下:
与 state 属性相关的逻辑,如下:
其中,当 state 的值为 false 时,会调用 changeLocation 方法。该方法比较重要,因为 push 和 replace 方法的实现也用到了它。
附录
相关的 type 和 interface 定义
state 相关
相关的 interface 定义如下:
Last updated