docs: add vue-router mount timing note with frontmatter
This commit is contained in:
parent
6073bbbfb8
commit
c2601d53ed
162
examples/blog/docs/notes/前端/vue-router-mount-timing.md
Normal file
162
examples/blog/docs/notes/前端/vue-router-mount-timing.md
Normal 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)
|
||||
Loading…
x
Reference in New Issue
Block a user