docs: add vue-router mount timing note with frontmatter

This commit is contained in:
yuany3721 2026-04-12 12:34:57 +08:00
parent 6073bbbfb8
commit c2601d53ed

View File

@ -0,0 +1,162 @@
---
title: Vue App 挂载与 Router beforeEach 时序关系
createTime: 2026/04/12 12:05:23
tags:
- Vue
- Vue Router
---
## 问题背景
在使用 Vue 3 + Vue Router 开发应用时,遇到了一个**时序问题**
::: warning 现象
`App.vue` 中的 `onMounted` 钩子**先于** `router.beforeEach` 路由守卫执行,导致在路由守卫中进行的异步权限校验还未完成时,页面逻辑已经开始执行。
:::
## 问题复现
```ts title="main.ts"
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
const app = createApp(App);
app.use(router); // 安装路由
app.mount("#app"); // 挂载应用
```
```ts title="router/index.ts"
router.beforeEach(async (to, from) => {
// 异步请求:获取用户权限
await fetchUserPermission(); // 假设耗时 500ms
// ... ...
});
```
```vue title="App.vue"
<script setup>
import { onMounted } from "vue";
onMounted(() => {
console.log("App.vue mounted"); // 这里先执行!
});
</script>
```
## 问题分析
按照 Vue Router 文档,标准执行顺序应该是:
```text
app.use(router) → app.mount() → beforeEach → 组件挂载
```
但在 `beforeEach` 中加入异步请求后,执行顺序变成了:
```text
app.use(router) → app.mount() → App.vue mounted → beforeEach 完成
```
`beforeEach` 虽然是异步解析,但它**不会阻塞**根组件 `App.vue` 的同步挂载流程。
`beforeEach` 守卫**只阻塞路由匹配的组件创建**,如果路由组件直接配置为 `App.vue` `<div id="app"></div>`的挂载并不会被阻塞。根本原因在于根组件 `App.vue``app.mount()` 时已经**同步创建和挂载**
## 解决方案
把业务逻辑拆分,`App.vue`只作为静态挂载点存在,实际业务逻辑用`<RouterView />` 控制,`<RouterView />` 的创建和渲染可以被正常阻塞,`App.vue` 自身的生命周期不受影响。
```vue title="App.vue现在只是空壳"
<template>
<RouterView />
</template>
<script setup>
// 不放业务逻辑,或只放全局布局逻辑
</script>
```
```json title="router/index.ts"
{
path: '/',
component: () => import('./views/Layout/MainLayout.vue'),
children: [
{ path: '', component: Home },
{ path: 'about', component: About }
]
}
```
```vue title="views/Layout/MainLayout.vue业务逻辑在这里"
<template>
<div class="main-layout">
<Header />
<RouterView />
<Footer />
</div>
</template>
<script setup>
import { onMounted } from "vue";
onMounted(() => {
// [!code highlight:2]
// ✅ 这里的逻辑一定在路由导航完成后执行
// 因为 beforeEach → 放行 → 才创建这个组件
});
</script>
```
## 其它方案
### 挂载前完成异步初始化
将关键异步逻辑提前到挂载之前:
```ts title="main.ts"
async function init() {
// 1. 先完成异步初始化
await fetchUserPermission();
// 2. 再安装路由
app.use(router);
// 3. 最后挂载
app.mount("#app");
}
init();
```
### 使用 `router.isReady()`
`App.vue` 中等待路由就绪:
```vue title="App.vue"
<template>
<div v-if="!isRouterReady" class="loading">Loading...</div>
<RouterView v-else />
</template>
<script setup>
import { ref, onMounted } from "vue";
import { useRouter } from "vue-router";
const router = useRouter();
const isRouterReady = ref(false);
onMounted(async () => {
await router.isReady();
isRouterReady.value = true;
});
</script>
```
### 使用 `router.beforeResolve`
`router.beforeResolve``router.beforeEach` 类似,因为它在每次导航时都会触发,不同的是,解析守卫刚好会在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。
## 相关链接
- [Vue Router 导航守卫官方文档](https://router.vuejs.org/zh/guide/advanced/navigation-guards.html)
- [Vue 3 生命周期钩子](https://cn.vuejs.org/api/composition-api-lifecycle.html)