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 实例的各个属性的来源。如下:

RouterHistory 接口定义的属性和方法
来源
  1. base 规范化

  2. go

  3. createHref

createWebHistory()

  1. location

  2. state

  3. push()

  4. replace()

useHistoryStateNavigation()

  1. listen

  2. destroy

  3. pauseListeners

useHistoryListeners()

1.3 逻辑分析

createWebHistory() 方法的底层是依赖 Web History API,基于对 Web History API 的理解,关于该方法,需要重点关注以下逻辑:

  1. popstate 事件的处理,以及如何和组件挂钩的

  2. 对 Router History 栈的维护,因为它必然不能直接用原生的 session history stack

  3. 与 Router Matcher 的配合,因为理论上是先匹配找到下标,然后再用原生的 history.go(num) 方法实现路由跳转的

2. 监听 popstate 事件

经过搜索发现,和 popstate 事件相关的逻辑都在 useHistoryListeners() 里。

2.1 useHistoryListeners()

简化后的代码,如下:

2.2 私有方法 popStateHandler

预期是在这里完成的前端视图切换。

在这个事件监听器函数中,和切换前端视图相关的逻辑就一个,那就是:执行了 listeners 里的回调函数。

其中,listeners 里的回调函数是通过公有方法 listen() 动态 push 的。

接下来我们重点看下 listeners 里的回调函数是什么样子的,以及它们是什么时候存进来的。

2.3 调用方法 listen()

useHistoryListeners() 暴露出去的公有方法 listen(),最终是通过 createWebHistory() 方法暴露出去的,即 RouterHistory 实例的 listen() 方法。

搜索 routerHistory.listen,结果如下:

也就是说在 createRouter() 时就 push 好了 listeners。接下来,看看它是如何使用参数 delta 以及如何切换前端视图的。

routerHistory.listen() 中,和 info.delta 相关的逻辑大部分是在 navigate() 的异常处理里,故暂且忽略。我们重点关注对参数 to 的处理,与之相关的代码如下:

至此,在响应 popstate 事件时执行的监听回调中,切换前端视图的任务主要由这两个方法完成:

  1. pushWithRedirect() 重定向导航

  2. navigate() 普通导航

对于这两个方法的介绍,详见 createRouter()

3. 维护 Router History 栈

经过搜索发现,和 history.pushState(), history.replaceState() 方法相关的逻辑都在 useHistoryStateNavigation() 里。

3.1 useHistoryStateNavigation()

简化后的代码,如下:

4. 路由跳转

经过搜索发现,和 history.back(), history.forward(), history.go() 方法相关的逻辑入口都在 RouterHistory 接口的定义里。

公共逻辑

处理 base 和 href

useHistoryStateNavigation(base)

该方法根据 base 返回一个包含 location, state 属性和 push, replace 方法的对象。

location 属性相关的逻辑,如下:

state 属性相关的逻辑,如下:

其中,当 state 的值为 false 时,会调用 changeLocation 方法。该方法比较重要,因为 pushreplace 方法的实现也用到了它。

附录

相关的 typeinterface 定义

state 相关

相关的 interface 定义如下:

Last updated