各位靓仔靓女们,晚上好!今天咱们来聊聊 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 永远消失!