Vue3+Vite中router.push()后URL变更但组件未渲染求助
Vue3 + Vue-Router路由跳转后URL变更但组件不渲染问题排查与解决
问题概述
使用Vue3(3.2.45)、Vite(4.0.0)搭配vue-router(^4.1.6)开发项目时,调用router.push()后URL有时会更新,但对应组件未渲染。已尝试给<router-view>添加:key属性,问题仍存在。
依赖版本
vue: ^3.2.45 vue-router: ^4.1.6 vite: ^4.0.0
项目配置代码
vite.config.ts
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite' import path from 'path' // https://vitejs.dev/config/ export default defineConfig({ envDir: './env', plugins: [ vue(), VueI18nPlugin({ include: path.resolve(__dirname, './src/locales/**') }) ], build: { sourcemap: false }, resolve: { alias: { '@': path.resolve(__dirname, './src') } }, optimizeDeps: { exclude: ['flatpickr/dist/types'] } })
router.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' import { CheckAuthType, RouteName } from '../types/enums/system' import loggedInRoutes from './LoggedIn/loggedInRoutes' const Error404 = () => import('../views/Error/Error404.vue') const Login = () => import('../views/Auth/Login.vue') const RegisteredHome = () => import('../views/Home/RegisteredHome.vue') import { SessionManager } from '../managers' const routes: Array<RouteRecordRaw> = [ { path: '/:pathMatch(.*)*', name: RouteName.error404, component: Error404 }, { path: '/', redirect: (to) => { console.log('TEST ROOT REDIRECT TO: ', to.name) return { name: RouteName.login } } }, { name: RouteName.login, path: '/login', component: Login, beforeEnter: (to, from, next) => { const sessionExist = SessionManager.sessionExist() console.log(`TEST BEFORE ENTER LOGIN TO: ${to.name as string} FROM: ${from.name as string} SESSION: ${sessionExist}`) if (sessionExist) { next({ name: RouteName.dashboard }) } else { next() } } }, { path: '/dsh', component: RegisteredHome, props: { main: true }, meta: { requireAuth: true }, name: RouteName.registeredHome, children: loggedInRoutes } ] const router = createRouter({ history: createWebHistory(), routes, scrollBehavior(to, from, savedPosition) { return savedPosition || { left: 0, top: 0 } } }) router.beforeEach((to, from, next) => { const sessionExist = SessionManager.sessionExist() console.log(`TEST BEFORE EACH TO: ${to.name as string} FROM: ${from.name as string} SESSION: ${sessionExist}`) if (to.matched.some((record) => record.meta.requireAuth)) { if (!sessionExist) { next({ name: RouteName.login }) } else { if (to.meta.authKey) { if (Array.isArray(to.meta.authKey) && to.meta.authKey.length) { let checkCount = 0 to.meta.authKey.forEach((key) => { if (SessionManager.checkAuth(key)) { checkCount++ } }) if (to.meta.checkAuthType === CheckAuthType.EVERY) { if (checkCount === to.meta.authKey.length) { next() } else { next({ name: RouteName.unauthorized }) } } else { if (checkCount > 0) { next() } else { next({ name: RouteName.unauthorized }) } } } else if (SessionManager.checkAuth(to.meta.authKey as string)) { next() } else { next({ name: RouteName.unauthorized }) } } else { next() } } } else { next() } }) router.afterEach((to, from, failure) => { if (failure) { console.log('ROUTE ISSUE!', failure) } }) export default router
main.ts
import { createApp } from 'vue' import router from './router' import App from './App.vue' import '../src/assets/css/app.css' import Toast from 'vue-toastification' import { LocalizationManager } from './managers' import { vMaska } from 'maska' import moment from 'moment-timezone' import { BreadCrumbItem } from './types/classes/system' import 'vue-router' import { CheckAuthType } from './types/enums/system' import 'leaflet' declare module 'vue-router' { interface RouteMeta { authKey?: Array<string> | string checkAuthType?: CheckAuthType activeMenu?: string breadCrumb?: Array<BreadCrumbItem> } } declare module 'leaflet' { interface MarkerOptions { locationIndex?: number } } moment.tz.setDefault('Europe/Istanbul') const app = createApp(App).use(router) app.use(Toast) app.use(LocalizationManager.i18n.value) app.directive('maska', vMaska) app.mount('#app')
App.vue
<template> <router-view /> <LoadingOverlay /> </template> <script setup lang="ts"> import { EventManager, LocalizationManager, SessionManager } from './managers' import { useRoute, useRouter } from 'vue-router' import LoadingOverlay from './components/LoadingOverlay.vue' import { watch, onMounted } from 'vue' import { EventName, RouteName } from './types/enums/system' import { useI18n } from 'vue-i18n' const route = useRoute() const router = useRouter() const { t } = useI18n() LocalizationManager.changeTitle() const logoutAndGotoMain = () => { router.push({ name: RouteName.login }) } const changeTitle = () => { const projectName = import.meta.env.VITE_APP_PROJECT_NAME let pageName = route.name let temp = projectName if (pageName) { temp += ' | ' + t('PageNames.' + pageName.toString()) } document.title = temp } watch( () => route.name, () => { changeTitle() } ) if (import.meta.env.VITE_APP_DEBUG_SCREEN as boolean) { document.documentElement.classList.add('debug-screens') } onMounted(() => { EventManager.emitter.on(EventName.logoutAndGotoMain, logoutAndGotoMain) EventManager.emitter.on(EventName.changeTitle, changeTitle) if (SessionManager.sessionToken) { SessionManager.refreshSession() } }) </script>
RegisteredHome.vue
<template> <SideMenu> <div class="pt-6"> <router-view name="loggedIn"></router-view> </div> </SideMenu> </template> <script setup lang="ts"> import SideMenu from '@/layout/SideMenu.vue' </script>
LoggedInRoutes.ts
import { RouteRecordRaw } from 'vue-router' import { RouteName } from '@/types/enums/system' import DashboardRoutes from './DashboardRoutes' import SystemRoutes from './System' const Unauthorized = () => import('@/views/Error/Unauthorized.vue') const loggedInRoutes: Array<RouteRecordRaw> = [ { path: '/unauthorized', name: RouteName.unauthorized, components: { loggedIn: Unauthorized }, meta: { breadCrumb: [ { routeName: RouteName.unauthorized, active: true } ] } }, ...DashboardRoutes, ...SystemRoutes, ] export default loggedInRoutes
排查与解决方法
1. 给命名视图添加:key属性
项目使用了命名视图(RegisteredHome.vue中的<router-view name="loggedIn">),但仅给根<router-view>设置了:key,命名视图未配置。Vue-Router的命名视图需要单独设置:key来强制组件刷新。
修改RegisteredHome.vue:
<template> <SideMenu> <div class="pt-6"> <router-view name="loggedIn" :key="$route.fullPath" /> </div> </SideMenu> </template>
同时确保根<router-view>使用$route.fullPath作为:key(比$route.name更可靠,支持同路由名不同参数的场景):
修改App.vue:
<template> <router-view :key="$route.fullPath" /> <LoadingOverlay /> </template>
2. 修正子路由路径配置
LoggedInRoutes.ts中unauthorized路由的路径为/unauthorized,这会使其脱离RegisteredHome(路径/dsh)的嵌套,成为独立路由。如果需要嵌套,子路由路径应去掉开头的斜杠:
{ path: 'unauthorized', // 改为相对路径,对应/dsh/unauthorized name: RouteName.unauthorized, components: { loggedIn: Unauthorized }, meta: { breadCrumb: [ { routeName: RouteName.unauthorized, active: true } ] } },
若需/unauthorized作为独立路由,需将其移到router.ts的根路由数组中,而非作为RegisteredHome的子路由。
3. 检查路由守卫逻辑
- 确保
beforeEach和beforeEnter中next()仅调用一次,避免多次跳转导致路由状态异常。 - 查看
router.afterEach中的failure日志,确认是否存在路由跳转失败的情况,日志会直接提示失败原因。
4. 验证异步组件加载
给异步组件添加错误捕获,排查是否存在组件加载失败的情况:
const Unauthorized = () => import('@/views/Error/Unauthorized.vue').catch(err => { console.error('加载Unauthorized组件失败:', err) return import('@/views/Error/Error404.vue') })
5. 排查响应式丢失问题
检查组件中是否存在未用ref/reactive声明的响应式数据,这类数据不会在路由变化时触发组件更新。
验证步骤
- 打开浏览器控制台,查看
ROUTE ISSUE!日志,确认是否有路由跳转失败。 - 在路由守卫中添加更详细的日志,追踪
to/from路由的参数、名称变化,确认跳转逻辑符合预期。 - 临时注释路由守卫代码,测试基础跳转是否正常,逐步缩小问题范围。
内容的提问来源于stack exchange,提问作者Taha Yerlikaya
相关产品推荐
相关产品推荐

