Vue Router实战中那些你必须掌握的路由控制技巧

司空伊可 框架 阅读 1,128
赞 10 收藏
二维码
手机扫码查看
反馈

我的写法,亲测靠谱

用 Vue Router 也快五年了,从 2.x 到 3.x 再到现在的 4.x(配合 Vue 3),踩过的坑能填满半个会议室。说实话,Vue Router 本身不难,但很多团队一上来就乱写,路由配置像意大利面条一样缠在一起,后期维护简直噩梦。我现在的项目里,路由这块基本稳定下来了,分享下我目前的写法。

Vue Router实战中那些你必须掌握的路由控制技巧

首先,别把所有路由都塞在一个文件里。很多人图省事,把几十个页面全堆在 router/index.js 里,看起来是方便了,但一旦要拆权限、做懒加载、或者改结构,就得硬着头皮翻几百行代码。我的做法是:按模块拆分路由文件,然后动态导入合并。

// router/modules/user.js
export default [
  {
    path: '/user/profile',
    name: 'UserProfile',
    component: () => import('@/views/user/Profile.vue')
  },
  {
    path: '/user/settings',
    name: 'UserSettings',
    component: () => import('@/views/user/Settings.vue')
  }
]

// router/modules/admin.js
export default [
  {
    path: '/admin/dashboard',
    name: 'AdminDashboard',
    component: () => import('@/views/admin/Dashboard.vue'),
    meta: { requiresAdmin: true }
  }
]

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import userRoutes from './modules/user'
import adminRoutes from './modules/admin'

const routes = [
  { path: '/', redirect: '/home' },
  { path: '/home', component: () => import('@/views/Home.vue') },
  ...userRoutes,
  ...adminRoutes,
  { path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('@/views/404.vue') }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

这种写法的好处很明显:谁负责哪个模块,就只动自己的路由文件;合并逻辑清晰,不会互相干扰;而且天然支持按需加载,首屏性能也好。

这几种错误写法,别再踩坑了

下面这些是我见过最多、也最容易出问题的写法,新手老手都容易栽进去。

1. 在路由守卫里直接跳转而不 return

很多人写全局前置守卫,验证登录状态,发现没登录就 router.push('/login'),但忘了 return next(false) 或者直接 return。结果就是:页面跳转了,但原来的导航还在继续执行,可能触发组件里的请求,甚至导致白屏或报错。

// 错误写法 ❌
router.beforeEach((to, from, next) => {
  if (!isAuthenticated && to.meta.requiresAuth) {
    router.push('/login') // 这里没 return!
  }
  next() // 还会继续走!
})

// 正确写法 ✅
router.beforeEach((to, from, next) => {
  if (!isAuthenticated && to.meta.requiresAuth) {
    next('/login') // 用 next 跳转,并终止当前导航
    return
  }
  next()
})

2. 动态添加路由后不处理重复注册

有些项目要做菜单动态生成,会在登录后调接口拿到路由表,然后用 router.addRoute() 添加。但如果你不做去重判断,用户刷新页面后再登录,就会重复添加,导致同一个路径匹配多个组件,控制台疯狂报 warning,严重时页面直接挂掉。

我的处理方式是在添加前先清掉已有的动态路由(比如打个标记),或者干脆每次登录前重置路由:

// 登录成功后
function resetDynamicRoutes(routesFromServer) {
  const dynamicRouteNames = router.getRoutes().map(r => r.name)
  dynamicRouteNames.forEach(name => {
    if (name && typeof name === 'string' && name.startsWith('dynamic-')) {
      router.removeRoute(name)
    }
  })

  routesFromServer.forEach(routeConfig => {
    router.addRoute(routeConfig)
  })
}

3. 用 params 传参却不配占位符

这个坑我踩过好几次。比如想通过 /user/:id 访问用户页,但在路由定义里写成 path: '/user',然后在跳转时用 router.push({ name: 'User', params: { id: 123 } })。结果就是参数根本传不过去,因为 path 里没有 :id 占位符,Vue Router 直接忽略 params。

记住:params 只对 path 中有对应参数占位符的路由生效。如果只是临时传参,建议用 query

实际项目中的坑

除了上面那些典型错误,实际开发中还有一些细节特别容易忽略。

滚动行为别硬写死。很多人为了“回到顶部”,在 createRouter 里直接写:

scrollBehavior(to, from, savedPosition) {
  return { top: 0 }
}

看起来没问题,但如果用户是从详情页点浏览器返回,期望回到列表页的上次滚动位置,这个写法就破坏了体验。我的做法是结合 savedPosition

scrollBehavior(to, from, savedPosition) {
  if (savedPosition) {
    return savedPosition
  } else {
    return { top: 0 }
  }
}

404 页面的写法也有讲究。很多人把通配路由写成 path: '*',这在 Vue Router 3 是对的,但在 Vue Router 4 里已经废弃了,必须用 /:pathMatch(.*)*。否则你访问不存在的路径,根本不会匹配到 404 组件。

还有,别在路由组件里做重度初始化逻辑。比如在 mounted 里发请求、初始化地图等。因为 Vue Router 默认会复用组件(比如从 /user/1 切到 /user/2,组件实例不变),这时候 mounted 不会重新执行,数据就错了。正确的做法是监听 $route 变化,或者用 watch

export default {
  watch: {
    '$route'(to, from) {
      if (to.params.id !== from.params.id) {
        this.fetchUserData(to.params.id)
      }
    }
  },
  created() {
    this.fetchUserData(this.$route.params.id)
  }
}

或者更现代一点,用组合式 API:

import { watch } from 'vue'
import { useRoute } from 'vue-router'

export default {
  setup() {
    const route = useRoute()
    const userData = ref(null)

    const fetchUserData = async (id) => {
      userData.value = await fetch(https://jztheme.com/api/user/${id}).then(r => r.json())
    }

    watch(() => route.params.id, (newId) => {
      fetchUserData(newId)
    }, { immediate: true })
  }
}

懒加载和预加载的平衡

很多人以为用了 () => import(...) 就万事大吉,其实不然。如果首页依赖太多子路由,首次加载还是慢。我的策略是:

  • 首页相关组件同步引入(牺牲一点打包体积,换首屏速度)
  • 非核心页面全部懒加载
  • 关键路径(比如用户大概率会点的下一步)做预加载,比如在 hover 或 mousedown 时提前加载 chunk

预加载可以这样搞:

const Profile = () => import('@/views/user/Profile.vue')

// 在父组件里
onMounted(() => {
  // 预加载
  Profile()
})

虽然会多发一个请求,但用户体验提升明显,尤其是网络差的时候。

以上是我这几年用 Vue Router 总结的一些实战经验,有些方案不是最优雅的,但胜在稳定、易维护。毕竟项目上线后没人关心你代码多漂亮,只关心别崩。

以上是我个人对 Vue Router 的完整讲解,有更优的实现方式欢迎评论区交流。这个技巧的拓展用法还有很多,后续会继续分享这类博客。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论