深入分析 Vue Router 源码中 `useRouter` 和 `useRoute` Composables 的实现,以及它们如何提供路由实例和当前路由的信息。

各位靓仔靓女们,晚上好!今天咱们来聊聊 Vue Router 里的两个宝藏 composable:useRouteruseRoute。 这俩哥们儿,一个提供路由实例,一个提供当前路由信息,用起来那是相当顺手。 但你知道它们背后的原理吗?今天咱们就扒开它们的源码,看看这些看似简单的 API,背后都藏了些啥。

第一章:路由的江湖地位

在深入 useRouteruseRoute 之前,咱们先简单回顾一下 Vue Router 在整个 Vue 应用里的地位。 路由的作用,简单说,就是根据不同的 URL,渲染不同的组件。 就像导航一样,指引用户在不同的页面间穿梭。

<template>
  <router-link to="/">Home</router-link>
  <router-link to="/about">About</router-link>
  <router-view></router-view>
</template>

router-link 负责生成链接,router-view 负责渲染组件。 Vue Router 背后维护着一个路由表,记录着 URL 和组件之间的映射关系。 当 URL 改变时,Vue Router 会找到对应的组件,并将其渲染到 router-view 中。

第二章:useRouter:路由实例的魔法棒

useRouter 的作用很简单,就是返回 Vue Router 的实例。 有了这个实例,咱们就可以用代码控制路由跳转,例如 router.push('/about')

那么,useRouter 是怎么拿到这个路由实例的呢? 答案就在 Vue 的 provide/inject 机制里。

咱们先来看看 createRouter 的实现(简化版):

import { createRouter as _createRouter, createWebHistory } from 'vue-router'
import { inject } from 'vue'

const RouterKey = Symbol('router')

export function createRouter(options) {
  const router = _createRouter(options);

  router.install = (app) => {
    app.provide(RouterKey, router);
    app.config.globalProperties.$router = router;
  }

  return router;
}

export function useRouter() {
  return inject(RouterKey);
}

看到了没? createRouter 函数内部调用了 _createRouter 创建了一个路由实例,并且定义了一个 install 函数。这个 install 函数是 Vue插件的标准写法, Vue在 app.use(router) 时候会调用这个install 函数。在 install 函数里, 通过 app.provide(RouterKey, router),将路由实例 router 注入到整个 Vue 应用中。这里的 RouterKey 是一个 Symbol,用来避免命名冲突。 同时,也挂载了 $routerapp.config.globalProperties,这样就能在组件中使用 this.$router

然后,useRouter 函数做的就是通过 inject(RouterKey),从 Vue 应用中取出之前注入的路由实例。 简单粗暴,直接拿到。

总结一下 useRouter 的工作流程:

步骤 说明 代码
1 创建路由实例,并将其注入到 Vue 应用中 (通过 provide) app.provide(RouterKey, router)
2 useRouter 函数通过 inject 从 Vue 应用中取出路由实例 inject(RouterKey)

第三章:useRoute:当前路由信息的百宝箱

useRoute 的作用是返回当前路由的信息,包括 URL、参数、查询参数等等。 有了它,咱们就可以根据当前路由的状态,做一些动态的事情。

<template>
  <p>当前路由:{{ route.path }}</p>
  <p>用户 ID:{{ route.params.id }}</p>
</template>

<script setup>
import { useRoute } from 'vue-router';

const route = useRoute();
</script>

useRoute 的实现比 useRouter 稍微复杂一点,因为它需要监听路由的变化,并更新路由信息。

咱们还是先从 createRouter 函数入手:

import { readonly, ref, computed, watch, onUnmounted } from 'vue'
import { createRouter as _createRouter, createWebHistory } from 'vue-router'
import { inject, getCurrentInstance } from 'vue'

const RouterKey = Symbol('router')
const RouteKey = Symbol('route')

export function createRouter(options) {
  const router = _createRouter(options);

  const currentRoute = ref(router.resolve(router.currentRoute.value));

  router.install = (app) => {
    app.provide(RouterKey, router);
    app.provide(RouteKey, readonly(currentRoute));
    app.config.globalProperties.$router = router;
    app.config.globalProperties.$route = currentRoute.value;

    // 监听路由变化,更新 currentRoute
    watch(
        () => router.currentRoute.value,
        (newRoute) => {
          currentRoute.value = router.resolve(newRoute);
        },
        { immediate: true }
    )
  }

  return router;
}

export function useRoute() {
  return inject(RouteKey);
}

这里多了几个关键点:

  1. currentRoute 这是一个 ref 对象,用来保存当前路由的信息。 初始值是通过 router.resolve(router.currentRoute.value) 获取的。 router.resolve 的作用是将一个路由对象转换成一个规范化的路由对象,包含完整的 URL 信息。
  2. readonly(currentRoute) 通过 readonlycurrentRoute 变成只读的。 这是为了防止组件直接修改路由信息,保证路由状态的统一性。
  3. app.provide(RouteKey, readonly(currentRoute)) 将只读的 currentRoute 注入到 Vue 应用中。
  4. watch 监听 router.currentRoute.value 的变化。 当路由发生变化时,会调用 router.resolve 重新解析路由信息,并更新 currentRoute.value

useRoute 函数做的仍然是通过 inject(RouteKey),从 Vue 应用中取出之前注入的 currentRoute 对象。

总结一下 useRoute 的工作流程:

步骤 说明 代码
1 创建一个 ref 对象 currentRoute,保存当前路由信息 const currentRoute = ref(router.resolve(router.currentRoute.value))
2 通过 readonlycurrentRoute 变成只读的 readonly(currentRoute)
3 将只读的 currentRoute 注入到 Vue 应用中 (通过 provide) app.provide(RouteKey, readonly(currentRoute))
4 监听路由变化,更新 currentRoute 的值 watch(() => router.currentRoute.value, (newRoute) => { currentRoute.value = router.resolve(newRoute); })
5 useRoute 函数通过 inject 从 Vue 应用中取出 currentRoute 对象 inject(RouteKey)

第四章:更深入的探讨:router.currentRoute 的奥秘

你可能注意到了,上面代码中多次出现了 router.currentRoute.value。 这个 router.currentRoute 是什么来头? 它又是怎么更新的呢?

router.currentRoute 是一个 ref 对象,它保存着当前路由的原始信息。 它的更新是由 Vue Router 内部的路由导航机制控制的。

简单来说,当用户点击 router-link,或者调用 router.push 等方法时,Vue Router 会启动一个路由导航流程。 在这个流程中,Vue Router 会:

  1. 解析新的 URL。
  2. 检查是否有对应的路由记录。
  3. 执行导航守卫(例如 beforeEachbeforeRouteEnter 等)。
  4. 更新 router.currentRoute 的值。
  5. 渲染新的组件。

router.currentRoute 的更新是整个路由导航流程的核心环节。 正是因为它的更新,useRoute 才能监听到路由的变化,并更新 currentRoute 的值。

第五章:源码之外:一些思考

通过对 useRouteruseRoute 源码的分析,咱们可以学到很多东西:

  • provide/inject 的妙用: Vue 的 provide/inject 机制是实现依赖注入的利器。 通过它,咱们可以在整个 Vue 应用中共享数据和状态。
  • refreadonly 的配合: ref 用来创建响应式数据,readonly 用来保护数据不被意外修改。 它们的配合使用,可以保证数据的可控性和一致性。
  • watch 的强大: watch 可以监听数据的变化,并在数据变化时执行回调函数。 它是实现响应式更新的重要手段。
  • Vue Router 的架构设计: Vue Router 的架构设计非常精巧,它将路由管理和组件渲染分离,并通过一系列 API 将它们连接起来。

第六章:总结与展望

今天咱们一起深入分析了 Vue Router 源码中 useRouteruseRoute 的实现。 这两个 composable 虽然简单,但它们背后却蕴含着丰富的知识和技巧。

希望通过今天的分享,大家能够对 Vue Router 的原理有更深入的了解,并在实际开发中更加灵活地使用它们。

当然,Vue Router 的源码远不止这些。 还有很多细节值得咱们去探索和学习。 希望大家能够继续深入研究,成为 Vue Router 的专家。

最后,祝大家编程愉快,bug 永远消失!

发表回复

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