Vue 3 Composition API的setup()中如何使用onBeforeRouteEnter实现路由准入校验?
闪烁根本原因
onBeforeMount、onMounted钩子触发时,路由已经完成导航切换,组件已经进入初始化渲染流程,异步请求返回错误后再触发跳转的时间差里,组件内容已经被部分渲染,就会出现闪烁。
推荐解决方案(按优先级排序)
方案1:使用路由独享守卫beforeEnter(无闪烁,最优解)
把数据校验逻辑放到路由配置的beforeEnter守卫中,在进入组件前就完成数据校验,不满足条件直接跳转,完全不会触发目标组件的渲染流程。
// 路由配置文件 import { useStore } from 'vuex' import { useRouter } from 'vue-router' const routes = [ { path: '/partner/:id', name: 'PartnerDetail', component: () => import('@/views/PartnerDetail.vue'), beforeEnter: async (to, from, next) => { const id = to.params.id as string const store = useStore() try { const resolve = await store.dispatch('PartnersModule/fetchPartnerById', id) // 数据存在就将预加载的数据存入路由meta,直接进入路由 to.meta.fetchedData = resolve next() } catch (error) { console.error(error, "Error fetching list") // 数据不存在直接跳转,不会加载目标组件,无闪烁 next({name: String(to.meta.backRoute)}) } } } ]
组件内直接取用预加载的数据即可,无需重复发请求:
// 目标组件setup内 const route = useRoute() const fetchedData = route.meta.fetchedData as contentInterface const content = ref(fetchedData.info) const employers = ref(fetchedData.employers) const branches = ref(fetchedData.branches) const contentLoading = ref(false)
方案2:使用组件内路由守卫onBeforeRouteEnter
Vue Router 4.x 支持在组件setup内使用onBeforeRouteEnter守卫,执行时机在组件实例创建前,校验不通过直接跳转也不会触发组件渲染:
import { onBeforeRouteEnter, useRoute } from 'vue-router' import { useStore } from 'vuex' import { ref } from 'vue' const content = ref() const employers = ref() const branches = ref() const contentLoading = ref(true) onBeforeRouteEnter(async (to, from, next) => { const store = useStore() const id = to.params.id as string try { const resolve = await store.dispatch('PartnersModule/fetchPartnerById', id) // 校验通过,通过next回调给组件实例赋值 next(vm => { content.value = resolve.info employers.value = resolve.employers branches.value = resolve.branches contentLoading.value = false }) } catch (error) { console.error(error, "Error fetching list") next({name: String(to.meta.backRoute)}) } })
方案3:组件内钩子兼容方案
如果必须使用onBeforeMount/onMounted实现,可以给组件根节点加渲染判断,加载完成前完全不显示业务内容,避免闪烁:
<template> <!-- 只有数据加载成功才渲染业务内容 --> <div v-if="!contentLoading && content"> <!-- 原有业务代码 --> </div> <!-- 加载过程显示全屏占位,遮挡中间态内容 --> <div v-else class="fullscreen-loading" style="width: 100vw;height: 100vh;background: #fff;"></div> </template>
逻辑部分保持原有写法即可。
内容的提问来源于stack exchange,提问作者Scrz
相关产品推荐
相关产品推荐

