外观
Vue App 挂载与 Router beforeEach 时序关系
2026-04-12
问题背景
在使用 Vue 3 + Vue Router 开发应用时,遇到了一个时序问题:
现象
App.vue 中的 onMounted 钩子先于 router.beforeEach 路由守卫执行,导致在路由守卫中进行的异步权限校验还未完成时,页面逻辑已经开始执行。
问题复现
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"); // 挂载应用router/index.ts
router.beforeEach(async (to, from) => {
// 异步请求:获取用户权限
await fetchUserPermission(); // 假设耗时 500ms
// ... ...
});App.vue
<script setup>
import { onMounted } from "vue";
onMounted(() => {
console.log("App.vue mounted"); // 这里先执行!
});
</script>问题分析
按照 Vue Router 文档,标准执行顺序应该是:
app.use(router) → app.mount() → beforeEach → 组件挂载但在 beforeEach 中加入异步请求后,执行顺序变成了:
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 自身的生命周期不受影响。
App.vue(现在只是空壳)
<template>
<RouterView />
</template>
<script setup>
// 不放业务逻辑,或只放全局布局逻辑
</script>router/index.ts
{
path: '/',
component: () => import('./views/Layout/MainLayout.vue'),
children: [
{ path: '', component: Home },
{ path: 'about', component: About }
]
}views/Layout/MainLayout.vue(业务逻辑在这里)
<template>
<div class="main-layout">
<Header />
<RouterView />
<Footer />
</div>
</template>
<script setup>
import { onMounted } from "vue";
onMounted(() => {
// ✅ 这里的逻辑一定在路由导航完成后执行
// 因为 beforeEach → 放行 → 才创建这个组件
});
</script>其它方案
挂载前完成异步初始化
将关键异步逻辑提前到挂载之前:
main.ts
async function init() {
// 1. 先完成异步初始化
await fetchUserPermission();
// 2. 再安装路由
app.use(router);
// 3. 最后挂载
app.mount("#app");
}
init();使用 router.isReady()
在 App.vue 中等待路由就绪:
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 类似,因为它在每次导航时都会触发,不同的是,解析守卫刚好会在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。
