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两个属性注入到每一个子组件中,并为路由组件开放beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave三个组件内守卫选项。读完本文,你将理解这三个 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 } })这段代码揭示了三个关键实现细节:
- 全局 mixin:通过
Vue.mixin({ beforeCreate }),每个组件创建时都会执行注入逻辑;只有根组件持有router选项,子组件通过this.$parent._routerRoot向上找到根实例。 - 响应式
_route:根实例上通过Vue.util.defineReactive(this, '_route', ...)定义响应式属性,路由切换时更新它,从而驱动所有组件重新渲染。 - 原型 getter:
$router与$route定义在Vue.prototype上,子组件经由_routerRoot间接访问根实例的数据。
$router是整个路由器实例,拥有push、replace、go、back、forward、beforeEach、afterEach等全部方法;$route是当前激活的 Route 对象。二者分工明确:前者负责"导航动作",后者描述"当前状态"。
二、$router:路由器实例
$router是new 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选择三种历史实现之一:html5(HTML5History)、hash(HashHistory)、abstract(AbstractHistory),并调用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深度冻结(query、params通过clone拷贝后同样不可变)。这正是文档所说"只读、不可变"的底层机制——每次导航成功后都会生成一个全新的冻结 Route 对象,而不是在原对象上修改。
$route对象包含以下属性(详见 Route 对象文档):
| 属性 | 类型 | 说明 |
|---|---|---|
$route.path | string | 当前路径,总是解析为绝对路径,如"/foo/bar" |
$route.params | Object | 动态路径段与通配符段的键值对;无参数时为空对象 |
$route.query | Object | 查询字符串键值对,如/foo?user=1时$route.query.user == 1 |
$route.hash | string | 当前路由的 hash(含#),无则空字符串 |
$route.fullPath | string | 包含 query 和 hash 的完整 URL |
$route.matched | Array<RouteRecord> | 匹配到的所有嵌套路由记录数组,父级在前、子级在后 |
$route.name | string | 当前路由的名称(使用命名路由时存在) |
由于$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 组件守卫在完整导航流程中的位置
根据导航守卫文档给出的完整解析流程,组件守卫分布在导航生命周期的不同阶段:
- 导航被触发
- 调用失活组件里的
beforeRouteLeave守卫 - 调用全局
beforeEach守卫 - 调用复用组件里的
beforeRouteUpdate守卫(2.2+) - 调用路由配置里的
beforeEnter - 解析异步路由组件
- 调用被激活组件里的
beforeRouteEnter守卫 - 调用全局
beforeResolve守卫(2.5+) - 导航被确认
- 调用全局
afterEach钩子 - 触发 DOM 更新
- 调用
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.push以Location对象(path+ 参数)发起导航、组件内用beforeRouteUpdate与beforeRouteLeave感知复用与离开。
六、相关资源
- 组件注入官方文档(本主题核心):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),仅供参考