vue-router 组件注入详解:$router、$route 与组件内导航守卫
2026/9/20 20:47:33 网站建设 项目流程

vue-router 组件注入详解:$router、$route 与组件内导航守卫

【免费下载链接】vue-router🚦 The official router for Vue 2项目地址: https://gitcode.com/gh_mirrors/vu/vue-router

本篇文章围绕 vue-router(Vue 2 官方路由)的组件注入机制展开:当你在根实例上以router选项传入路由器实例后,Vue Router 会自动把$router$route两个属性注入到每一个子组件中,并为路由组件开放beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave三个组件内守卫选项。读完本文,你将理解这三个 API 的确切语义、只读$route对象的内部实现(Object.freeze冻结)、以及它们在完整导航流程中的执行时机,并能在真实项目中正确使用它们。

本文以仓库内西班牙语文档 docs-gitbook/es/api/component-injections.md 为核心骨架,结合 src/install.js、src/util/route.js 等源码进行佐证与扩充。

一、注入机制概览:一切从router选项开始

组件注入(Component Injections)的核心前提非常简单:只要把router实例作为router选项传给根 Vue 实例,这些属性就会被注入到每个子组件中

const router = new VueRouter({ routes: [ { path: '/', component: Home } ] }) const app = new Vue({ router // 关键:传入 router 选项 }).$mount('#app')

从源码 src/install.js 可以看到注入的具体实现:

Vue.mixin({ beforeCreate () { if (isDef(this.$options.router)) { this._routerRoot = this this._router = this.$options.router this._router.init(this) Vue.util.defineReactive(this, '_route', this._router.history.current) } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this } } }) Object.defineProperty(Vue.prototype, '$router', { get () { return this._routerRoot._router } }) Object.defineProperty(Vue.prototype, '$route', { get () { return this._routerRoot._route } })

这段代码揭示了三个关键实现细节:

  1. 全局 mixin:通过Vue.mixin({ beforeCreate }),每个组件创建时都会执行注入逻辑;只有根组件持有router选项,子组件通过this.$parent._routerRoot向上找到根实例。
  2. 响应式_route:根实例上通过Vue.util.defineReactive(this, '_route', ...)定义响应式属性,路由切换时更新它,从而驱动所有组件重新渲染。
  3. 原型 getter$router$route定义在Vue.prototype上,子组件经由_routerRoot间接访问根实例的数据。

$router整个路由器实例,拥有pushreplacegobackforwardbeforeEachafterEach等全部方法;$route当前激活的 Route 对象。二者分工明确:前者负责"导航动作",后者描述"当前状态"。

二、$router:路由器实例

$routernew VueRouter({...})创建出来的实例,类型为 src/router.js 中导出的VueRouter类。在组件内你可以这样使用:

export default { methods: { goHome () { this.$router.push('/home') // 导航到 /home this.$router.replace('/login') // 替换当前历史记录 this.$router.go(-1) // 后退一步 } } }

从 src/router.js 可以看出,构造时会根据options.mode选择三种历史实现之一:html5HTML5History)、hashHashHistory)、abstractAbstractHistory),并调用createMatcher(options.routes || [], this)生成路由匹配器。也就是说,组件内调用this.$router.push()时,实际上走的是对应 history 实现的push流程,最终由 matcher 完成路由匹配与守卫链执行。

三、$route:只读且不可变的当前路由对象

$route是当前激活的 Route 对象。它有两个重要特性:

  • 只读:文档明确说明 "Esta propiedad es de solo lectura y sus propiedades son inmutables"(该属性是只读的,其属性不可变)。
  • 可观察:虽然不可修改,但它是响应式的,可以像普通 data 一样被watch观察。

源码 src/util/route.js 用一句话揭示了不可变性的实现根源:

return Object.freeze(route)

createRoute创建的路由对象会被Object.freeze深度冻结(queryparams通过clone拷贝后同样不可变)。这正是文档所说"只读、不可变"的底层机制——每次导航成功后都会生成一个全新的冻结 Route 对象,而不是在原对象上修改。

$route对象包含以下属性(详见 Route 对象文档):

属性类型说明
$route.pathstring当前路径,总是解析为绝对路径,如"/foo/bar"
$route.paramsObject动态路径段与通配符段的键值对;无参数时为空对象
$route.queryObject查询字符串键值对,如/foo?user=1$route.query.user == 1
$route.hashstring当前路由的 hash(含#),无则空字符串
$route.fullPathstring包含 query 和 hash 的完整 URL
$route.matchedArray<RouteRecord>匹配到的所有嵌套路由记录数组,父级在前、子级在后
$route.namestring当前路由的名称(使用命名路由时存在)

由于$route是响应式的,你可以用watch对参数或 query 变化做出反应:

watch: { $route (to, from) { // 对路由变化做出响应 }, '$route.params.id' (id) { // 更精确地观察某个参数 } }

需要特别注意的是:参数或 query 的变化不会触发导航守卫(见 导航守卫文档)。此时要么观察$route对象,要么使用组件内守卫beforeRouteUpdate(详见下文)。

四、组件内导航守卫(Opciones habilitadas)

除了全局守卫(router.beforeEach)与路由级守卫(beforeEnter),你还可以直接在路由组件内部定义以下三个守卫选项:

  • beforeRouteEnter:进入该路由前调用
  • beforeRouteUpdate(Vue Router 2.2+ 新增):路由已改变但组件被复用时调用
  • beforeRouteLeave:离开该路由前调用
const Foo = { template: `...`, beforeRouteEnter (to, from, next) { // 在渲染该组件的路由被确认前调用 // 此时组件实例尚未创建,因此无法访问 this! }, beforeRouteUpdate (to, from, next) { // 在当前路由改变、但该组件被复用时调用 // 例如 /foo/:id 从 /foo/1 导航到 /foo/2 时,同一组件实例被复用 // 此守卫可以访问 this }, beforeRouteLeave (to, from, next) { // 导航离开渲染该组件的路由时调用 // 可以访问 this } }

4.1 为什么beforeRouteEnter拿不到this

beforeRouteEnter在导航确认之前执行,此时目标组件尚未创建,因此没有this。如果需要访问组件实例,可以向next传入回调函数——该回调会在导航确认后执行,并把实例作为参数传入:

beforeRouteEnter (to, from, next) { next(vm => { // 通过 vm 访问组件实例 vm.loadData() }) }

从源码 src/util/route.js 的handleRouteEntered可以看到回调的执行时机:导航确认、DOM 更新后,遍历route.matched中每个 record 的enteredCbs,把已创建实例传入回调。这与导航守卫文档中完整流程的第 12 步一致:"调用beforeRouteEnter守卫传给next的回调函数,并传入创建好的实例"。

4.2beforeRouteUpdate:应对参数变化

这是 Vue Router 2.2 引入的守卫,专门解决动态路由复用问题。当路由/foo/:id/foo/1导航到/foo/2时,组件实例会被复用,beforeRouteUpdate此时被调用,且可以访问this

beforeRouteUpdate (to, from, next) { this.postId = to.params.id // 直接更新实例数据 next() }

4.3beforeRouteLeave:离开前的拦截

beforeRouteLeave最典型的场景是防止用户在未保存修改时意外离开,通过next(false)取消导航:

beforeRouteLeave (to, from, next) { if (this.formDirty) { const ok = window.confirm('你有未保存的修改,确定离开吗?') ok ? next() : next(false) // next(false) 取消导航 } else { next() } }

4.4 组件守卫的钩子合并机制

三个组件守卫都是组件选项,遵循 Vue 的选项合并策略。源码 src/install.js 特意将它们与生命周期钩子使用相同的合并策略:

strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created

这意味着这些守卫可以出现在 mixin 中,并与组件自身的守卫按数组形式合并、按顺序执行——这是组件守卫能参与导航链的基础。

4.5 组件守卫在完整导航流程中的位置

根据导航守卫文档给出的完整解析流程,组件守卫分布在导航生命周期的不同阶段:

  1. 导航被触发
  2. 调用失活组件里的beforeRouteLeave守卫
  3. 调用全局beforeEach守卫
  4. 调用复用组件里的beforeRouteUpdate守卫(2.2+)
  5. 调用路由配置里的beforeEnter
  6. 解析异步路由组件
  7. 调用被激活组件里的beforeRouteEnter守卫
  8. 调用全局beforeResolve守卫(2.5+)
  9. 导航被确认
  10. 调用全局afterEach钩子
  11. 触发 DOM 更新
  12. 调用beforeRouteEnter中传给next的回调(此时实例已创建)

务必总是调用next函数,否则导航链永远不会被解析,页面将停滞在当前状态。

五、综合实战:组合使用注入 API

下面是一个将$router$route与组件守卫组合使用的完整示例(模式参考仓库 examples/nested-routes 等示例目录):

const User = { props: ['id'], template: ` <div class="user"> <p>当前用户 ID:{{ $route.params.id }}</p> <button @click="$router.push({ path: '/users/' + (Number($route.params.id) + 1) })"> 下一个用户 </button> </div> `, beforeRouteUpdate (to, from, next) { // 组件复用时的参数变化在此处理 console.log(`从用户 ${from.params.id} 切换到用户 ${to.params.id}`) next() }, beforeRouteLeave (to, from, next) { if (confirm('确定离开用户页面吗?')) { next() } else { next(false) } } } const router = new VueRouter({ routes: [ { path: '/users/:id', component: User } ] }) new Vue({ router }).$mount('#app')

这段代码同时体现了三个要点:模板中通过$route.params.id读取动态参数、通过$router.pushLocation对象(path+ 参数)发起导航、组件内用beforeRouteUpdatebeforeRouteLeave感知复用与离开。

六、相关资源

  • 组件注入官方文档(本主题核心):docs-gitbook/es/api/component-injections.md
  • Route 对象完整属性说明:docs-gitbook/es/api/route-object.md
  • 导航守卫详解与完整解析流程:docs-gitbook/es/advanced/navigation-guards.md
  • 动态路由与参数变化处理:docs-gitbook/es/essentials/dynamic-matching.md
  • 注入机制实现源码:src/install.js
  • $route不可变实现(Object.freeze):src/util/route.js
  • 导航守卫源码:src/composables/guards.js

【免费下载链接】vue-router🚦 The official router for Vue 2项目地址: https://gitcode.com/gh_mirrors/vu/vue-router

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询