各位靓仔靓女们,晚上好!今天咱们来聊聊 Vue Router 里的两个宝藏 composable:useRouter 和 useRoute。 这俩哥们儿,一个提供路由实例,一个提供当前路由信息,用起来那是相当顺手。 但你知道它们背后的原理吗?今天咱们就扒开它们的源码,看看这些看似简单的 API,背后都藏了些啥。
第一章:路由的江湖地位
在深入 useRouter 和 useRoute 之前,咱们先简单回顾一下 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,用来避免命名冲突。 同时,也挂载了 $router 到 app.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);
}
这里多了几个关键点:
currentRoute: 这是一个ref对象,用来保存当前路由的信息。 初始值是通过router.resolve(router.currentRoute.value)获取的。router.resolve的作用是将一个路由对象转换成一个规范化的路由对象,包含完整的 URL 信息。readonly(currentRoute): 通过readonly将currentRoute变成只读的。 这是为了防止组件直接修改路由信息,保证路由状态的统一性。app.provide(RouteKey, readonly(currentRoute)): 将只读的currentRoute注入到 Vue 应用中。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 | 通过 readonly 将 currentRoute 变成只读的 |
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 会:
- 解析新的 URL。
- 检查是否有对应的路由记录。
- 执行导航守卫(例如
beforeEach、beforeRouteEnter等)。 - 更新
router.currentRoute的值。 - 渲染新的组件。
router.currentRoute 的更新是整个路由导航流程的核心环节。 正是因为它的更新,useRoute 才能监听到路由的变化,并更新 currentRoute 的值。
第五章:源码之外:一些思考
通过对 useRouter 和 useRoute 源码的分析,咱们可以学到很多东西:
provide/inject的妙用: Vue 的provide/inject机制是实现依赖注入的利器。 通过它,咱们可以在整个 Vue 应用中共享数据和状态。ref和readonly的配合:ref用来创建响应式数据,readonly用来保护数据不被意外修改。 它们的配合使用,可以保证数据的可控性和一致性。watch的强大:watch可以监听数据的变化,并在数据变化时执行回调函数。 它是实现响应式更新的重要手段。- Vue Router 的架构设计: Vue Router 的架构设计非常精巧,它将路由管理和组件渲染分离,并通过一系列 API 将它们连接起来。
第六章:总结与展望
今天咱们一起深入分析了 Vue Router 源码中 useRouter 和 useRoute 的实现。 这两个 composable 虽然简单,但它们背后却蕴含着丰富的知识和技巧。
希望通过今天的分享,大家能够对 Vue Router 的原理有更深入的了解,并在实际开发中更加灵活地使用它们。
当然,Vue Router 的源码远不止这些。 还有很多细节值得咱们去探索和学习。 希望大家能够继续深入研究,成为 Vue Router 的专家。
最后,祝大家编程愉快,bug 永远消失!