分析 Vue Router 源码中 `router.push`, `router.replace`, `router.go` 等编程式导航方法的实现。

各位老铁,大家好!今天咱来聊聊 Vue Router 里那些神出鬼没的导航方法:router.push, router.replace, router.go。 别看它们用起来简单,背后可藏着不少门道。咱们一起扒一扒它们的源码,看看这些家伙到底是怎么运作的。

一、先来个热身:Vue Router 的基本架构

想要理解 push, replace, go,咱们先得对 Vue Router 的整体架构有个大概的认识。简单来说,Vue Router 主要干了两件事:

  1. 监听 URL 变化: 通过 hashchangepopstate 事件,感知浏览器地址栏的变化。
  2. 更新视图: 根据 URL 变化,匹配对应的路由规则,渲染相应的组件。

这其中,history 对象扮演了关键角色。它提供了操作浏览器历史记录的 API,而 Vue Router 正是利用这些 API 来实现导航功能的。 Vue Router 提供了三种历史模式:

模式 说明 浏览器兼容性 SEO友好性
Hash 模式 URL 中带 # 符号,通过 hashchange 事件监听 URL 变化。兼容性好,但 URL 不美观。 兼容性好
History 模式 利用 history.pushStatehistory.replaceState API,实现 URL 的无刷新跳转。URL 美观,但需要服务器端配置支持。 较好
Abstract 模式 在非浏览器环境中(例如 Node.js),模拟 history 对象。 最好 看情况

咱们主要关注 History 模式,因为 push, replace, go 这些方法都直接操作 history 对象。

二、router.push: 勇往直前的探险家

router.push 就像一个勇敢的探险家,它会在浏览器历史记录中新增一条记录,然后跳转到新的 URL。 也就是说,你可以通过浏览器的前进/后退按钮回到之前的页面。

// 使用示例
router.push('/about') // 字符串形式
router.push({ path: '/users/123' }) // 对象形式
router.push({ name: 'user', params: { userId: '123' } }) // 命名路由
router.push({ path: '/register', query: { plan: 'private' } }) // 带查询参数

现在,让我们深入 router.push 的源码(简化版):

function push(location, onComplete, onAbort) {
  // 1. 规范化 location 参数 (处理字符串、对象等不同形式)
  const target = resolve(location, currentRoute, append); // 重要!

  // 2. 执行导航 (调用 transitionTo)
  transitionTo(target, onComplete, onAbort);
}

这段代码的核心是 resolvetransitionTo 这两个函数。

  • resolve(location, currentRoute, append): 这个函数负责将各种形式的 location 参数(字符串、对象)转换成一个标准化的路由对象。 它还会处理 append 属性,如果 appendtrue,则会将新的 URL 追加到当前 URL 后面。 这个函数还会对路径进行编码,避免出现特殊字符。

    //resolve部分代码
    function resolve (
      route: RawLocation,
      current?: Route,
      append?: boolean
    ): Route {
      let location = normalizeLocation(route, current, append)
      let resolvedRoute: Route = map[location.path]
      // 返回一个Route对象
      return resolvedRoute
    }

    normalizeLocation的作用是把多种格式的location统一处理成对象。

  • transitionTo(target, onComplete, onAbort): 这个函数是导航的核心。它会比较目标路由和当前路由,决定是否需要进行导航,以及如何进行导航。 如果需要导航,它会更新 history 对象(调用 history.pushState),并更新 Vue Router 的内部状态。

    function transitionTo (
      route: Route,
      onComplete?: Function,
      onAbort?: Function
    ) {
      // 1. 确认导航 (调用 confirmTransition)
      confirmTransition(route, () => {
    
        // 2. 更新历史记录 (调用 updateRoute)
        updateRoute(route)
    
        // 3. 调用 onComplete 回调
        if (onComplete) {
          onComplete(route)
        }
      }, onAbort)
    }

    transitionTo 函数中,confirmTransition 负责进行一系列的路由守卫检查(例如 beforeRouteEnter, beforeRouteUpdate, beforeRouteLeave),确保导航是安全的。 updateRoute 函数则负责更新 history 对象和 Vue Router 的内部状态,最终触发视图的更新。

三、router.replace: 低调的幕后替换者

router.replacerouter.push 很像,但它不会在浏览器历史记录中新增一条记录,而是直接替换掉当前记录。 就像一个低调的幕后替换者,悄无声息地改变了 URL,而且不留下任何痕迹。

// 使用示例
router.replace('/home')
router.replace({ path: '/profile' })

router.replace 的源码和 router.push 非常相似,唯一的区别在于它调用的是 history.replaceState 而不是 history.pushState

function replace(location, onComplete, onAbort) {
  const target = resolve(location, currentRoute, append);

  transitionTo(target, onComplete, onAbort);
}

//transitionTo 内部会调用 history.replaceState

由于 transitionTo 大部分逻辑和push相同,为了避免重复,这里就不再赘述。

router.pushrouter.replace 的区别:

方法 作用 是否新增历史记录
router.push 跳转到新的 URL,新增一条历史记录。
router.replace 跳转到新的 URL,替换当前历史记录。

四、router.go: 时间旅行的操控者

router.go 就像一个时间旅行的操控者,它可以让你在浏览器历史记录中前进或后退指定的步数。

// 使用示例
router.go(1) // 前进一页
router.go(-1) // 后退一页
router.go(0) // 刷新当前页面

router.go 的源码非常简单,它直接调用了 history.go 方法。

function go(n) {
  history.go(n)
}

这个 n 参数指定了前进或后退的步数。正数表示前进,负数表示后退,0 表示刷新当前页面。

五、总结:导航方法的选择

现在,咱们已经了解了 router.push, router.replace, router.go 的实现原理。那么,在实际开发中,我们应该如何选择这些方法呢?

  • router.push: 适用于大多数场景,例如页面跳转、表单提交等。
  • router.replace: 适用于一些特殊的场景,例如用户登录后,需要替换掉登录页面,避免用户通过后退按钮再次回到登录页面。
  • router.go: 适用于需要前进或后退的场景,例如浏览器的前进/后退按钮。
方法 适用场景
router.push 常规页面跳转,希望保留历史记录,允许用户通过前进/后退按钮导航。
router.replace 替换当前页面,不希望保留历史记录,例如登录后的重定向、某些特殊流程的跳转。
router.go 模拟浏览器的前进/后退功能,允许用户在历史记录中跳转。

六、一些额外的思考

  • 路由守卫: Vue Router 提供了丰富的路由守卫功能(例如 beforeRouteEnter, beforeRouteUpdate, beforeRouteLeave),可以在导航过程中进行权限验证、数据预取等操作。
  • 异步组件: 如果你的路由组件是异步加载的,那么需要在路由配置中指定 component 为一个返回 Promise 的函数。
  • 滚动行为: Vue Router 允许你自定义滚动行为,例如在页面跳转后滚动到顶部。

七、结尾

好了,今天的 Vue Router 导航方法源码分析就到这里了。希望通过这次深入的剖析,能让你对 Vue Router 的运作机制有更清晰的理解。 记住,掌握原理才能更好地应用! 咱们下期再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注