|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
引言
在现代前端开发中,Vue.js已经成为了最受欢迎的JavaScript框架之一,而Vue3作为其最新版本,带来了更多性能优化和新特性。Vue Router作为Vue.js官方的路由管理器,与Vue3紧密集成,为我们提供了构建单页应用(SPA)的强大能力。本文将深入探讨Vue3中Vue Router的高级用法,并通过实战案例展示如何构建一个可扩展、高性能的前端路由架构。
Vue3和Vue Router基础回顾
在深入高级用法之前,我们先简单回顾一下Vue3和Vue Router的基础知识。
Vue3核心特性
Vue3引入了许多新特性,其中最重要的是Composition API,它提供了一种更灵活的组织组件逻辑的方式:
- import { ref, computed, onMounted } from 'vue'
- export default {
- setup() {
- const count = ref(0)
- const doubleCount = computed(() => count.value * 2)
-
- function increment() {
- count.value++
- }
-
- onMounted(() => {
- console.log('Component mounted')
- })
-
- return {
- count,
- doubleCount,
- increment
- }
- }
- }
复制代码
Vue Router基础
Vue Router的基础用法包括定义路由、创建路由实例和挂载路由:
- // 1. 定义路由组件
- const Home = { template: '<div>Home</div>' }
- const About = { template: '<div>About</div>' }
- // 2. 定义路由
- const routes = [
- { path: '/', component: Home },
- { path: '/about', component: About }
- ]
- // 3. 创建路由实例
- const router = VueRouter.createRouter({
- history: VueRouter.createWebHistory(),
- routes
- })
- // 4. 创建并挂载根实例
- const app = Vue.createApp({})
- app.use(router)
- app.mount('#app')
复制代码
Vue Router高级特性详解
动态路由
动态路由允许我们根据不同的参数展示不同的内容,是构建可扩展路由架构的基础。
- const routes = [
- // 动态字段以冒号开始
- { path: '/user/:id', component: User }
- ]
复制代码
在组件中,我们可以通过useRoute获取路由参数:
- import { useRoute } from 'vue-router'
- export default {
- setup() {
- const route = useRoute()
- console.log(route.params.id) // 访问动态参数
- }
- }
复制代码
我们可以使用正则表达式来限制动态参数的匹配规则:
- const routes = [
- // 只匹配数字id
- { path: '/user/:id(\\d+)', component: User },
- // 可选参数
- { path: '/user/:id?', component: User },
- // 重复参数
- { path: '/user/:id(\\d+)+', component: User },
- // 自定义正则
- { path: '/user/:id(\\d{2,})', component: User }
- ]
复制代码
在大型应用中,我们可能需要根据用户权限或其他条件动态添加路由:
- // 添加路由
- router.addRoute({
- path: '/admin',
- component: Admin
- })
- // 添加嵌套路由
- router.addRoute('admin', {
- path: 'dashboard',
- component: AdminDashboard
- })
- // 检查路由是否存在
- console.log(router.hasRoute('admin')) // true
- // 删除路由
- router.removeRoute('admin')
复制代码
嵌套路由
嵌套路由允许我们在一个路由下嵌套子路由,构建复杂的页面结构。
- const routes = [
- {
- path: '/user',
- component: User,
- children: [
- {
- // 当 /user/profile 匹配成功
- // UserProfile 将被渲染到 User 的 <router-view> 内部
- path: 'profile',
- component: UserProfile
- },
- {
- // 当 /user/posts 匹配成功
- // UserPosts 将被渲染到 User 的 <router-view> 内部
- path: 'posts',
- component: UserPosts
- }
- ]
- }
- ]
复制代码
我们可以在一个路由中定义多个命名视图:
- const routes = [
- {
- path: '/settings',
- components: {
- default: Settings,
- a: Sidebar,
- b: Content
- }
- }
- ]
复制代码
在模板中:
- <router-view></router-view>
- <router-view name="a"></router-view>
- <router-view name="b"></router-view>
复制代码
命名路由
命名路由可以简化导航和编程式导航。
- const routes = [
- {
- path: '/user/:userId',
- name: 'user',
- component: User
- }
- ]
复制代码- <!-- 使用命名路由进行导航 -->
- <router-link :to="{ name: 'user', params: { userId: 123 } }">User</router-link>
复制代码- // 编程式导航
- router.push({ name: 'user', params: { userId: 123 } })
复制代码
重定向和别名
重定向和别名提供了灵活的URL映射方式。
- const routes = [
- { path: '/home', redirect: '/' },
- // 动态重定向
- { path: '/redirect-with-params/:id', redirect: to => {
- // 方法接收目标路由作为参数
- // return 重定向的字符串路径/路径对象
- return { path: '/user', query: { id: to.params.id } }
- }}
- ]
复制代码- const routes = [
- {
- path: '/user',
- component: User,
- alias: '/people' // /user 的别名是 /people
- }
- ]
复制代码
路由元信息
路由元信息允许我们附加自定义数据到路由记录上,这对于权限控制等功能非常有用。
- const routes = [
- {
- path: '/admin',
- component: Admin,
- meta: { requiresAuth: true, role: 'admin' }
- },
- {
- path: '/profile',
- component: Profile,
- meta: { requiresAuth: true }
- }
- ]
复制代码- // 在导航守卫中访问
- router.beforeEach((to, from, next) => {
- if (to.meta.requiresAuth && !isAuthenticated) {
- next('/login')
- } else {
- next()
- }
- })
复制代码
导航守卫
导航守卫允许我们在路由跳转前后执行代码,是实现权限控制、数据预取等功能的关键。
- router.beforeEach((to, from, next) => {
- // to: 即将进入的目标路由
- // from: 当前导航正要离开的路由
- // next: 必须调用该方法来 resolve 这个钩子
-
- if (to.meta.requiresAuth && !isAuthenticated) {
- next('/login')
- } else {
- next()
- }
- })
复制代码- router.beforeResolve(async to => {
- if (to.meta.requiresData) {
- await fetchData(to.params.id)
- }
- })
复制代码- router.afterEach((to, from) => {
- // 适合用于修改页面标题等操作
- document.title = to.meta.title || 'Default Title'
- })
复制代码- const routes = [
- {
- path: '/admin',
- component: Admin,
- beforeEnter: (to, from, next) => {
- if (!isAdmin()) {
- next('/login')
- } else {
- next()
- }
- }
- }
- ]
复制代码- export default {
- beforeRouteEnter(to, from, next) {
- // 在渲染该组件的对应路由被验证前调用
- // 不能获取组件实例 `this`!
- next(vm => {
- // 通过 `vm` 访问组件实例
- })
- },
- beforeRouteUpdate(to, from, next) {
- // 在当前路由改变,但是该组件被复用时调用
- this.name = to.params.name
- next()
- },
- beforeRouteLeave(to, from, next) {
- // 在导航离开该组件的对应路由时调用
- if (isDataSaved()) {
- next()
- } else {
- next(false) // 取消导航
- }
- }
- }
复制代码
构建可扩展的路由架构
模块化路由设计
在大型应用中,将路由按功能模块划分是提高可维护性的关键。
- // router/modules/user.js
- export default {
- path: '/user',
- name: 'User',
- component: () => import('@/views/user/index.vue'),
- meta: { title: '用户管理', icon: 'user' },
- children: [
- {
- path: 'list',
- name: 'UserList',
- component: () => import('@/views/user/list.vue'),
- meta: { title: '用户列表' }
- },
- {
- path: 'detail/:id',
- name: 'UserDetail',
- component: () => import('@/views/user/detail.vue'),
- meta: { title: '用户详情' },
- props: true
- }
- ]
- }
- // router/modules/product.js
- export default {
- path: '/product',
- name: 'Product',
- component: () => import('@/views/product/index.vue'),
- meta: { title: '产品管理', icon: 'product' },
- children: [
- {
- path: 'list',
- name: 'ProductList',
- component: () => import('@/views/product/list.vue'),
- meta: { title: '产品列表' }
- },
- {
- path: 'detail/:id',
- name: 'ProductDetail',
- component: () => import('@/views/product/detail.vue'),
- meta: { title: '产品详情' },
- props: true
- }
- ]
- }
复制代码- // router/index.js
- import { createRouter, createWebHistory } from 'vue-router'
- import userRoutes from './modules/user'
- import productRoutes from './modules/product'
- const routes = [
- {
- path: '/',
- redirect: '/dashboard'
- },
- {
- path: '/dashboard',
- name: 'Dashboard',
- component: () => import('@/views/dashboard/index.vue'),
- meta: { title: '首页', icon: 'dashboard' }
- },
- {
- path: '/login',
- name: 'Login',
- component: () => import('@/views/login/index.vue'),
- meta: { title: '登录' }
- },
- {
- path: '/404',
- name: '404',
- component: () => import('@/views/error/404.vue'),
- meta: { title: '404' }
- },
- userRoutes,
- productRoutes,
- {
- path: '/:pathMatch(.*)*',
- redirect: '/404'
- }
- ]
- const router = createRouter({
- history: createWebHistory(),
- routes
- })
- export default router
复制代码
路由权限控制
权限控制是企业级应用中必不可少的功能,我们可以通过路由元信息和导航守卫实现。
- // 权限配置
- const permission = {
- admin: ['user', 'product', 'dashboard'],
- editor: ['product', 'dashboard'],
- user: ['dashboard']
- }
- // 获取用户角色
- function getUserRole() {
- // 从localStorage或Vuex/Pinia中获取用户角色
- return localStorage.getItem('userRole') || 'user'
- }
- // 检查权限
- function hasPermission(route) {
- const role = getUserRole()
- const requiredPermission = route.meta && route.meta.permission
-
- if (!requiredPermission) {
- return true // 如果路由不需要权限,则允许访问
- }
-
- return permission[role].includes(requiredPermission)
- }
- // 全局前置守卫
- router.beforeEach((to, from, next) => {
- // 检查是否需要登录
- if (to.meta.requiresAuth && !isAuthenticated()) {
- next({ name: 'Login', query: { redirect: to.fullPath } })
- return
- }
-
- // 检查权限
- if (!hasPermission(to)) {
- next({ name: '403' }) // 假设有403页面
- return
- }
-
- next()
- })
复制代码
在更复杂的场景中,我们可能需要根据用户权限动态生成路由:
- // 动态添加路由
- function addPermissionRoutes() {
- const role = getUserRole()
-
- // 根据角色获取路由配置
- const permissionRoutes = getRoutesByRole(role)
-
- // 动态添加路由
- permissionRoutes.forEach(route => {
- router.addRoute(route)
- })
- }
- // 在应用启动时调用
- addPermissionRoutes()
复制代码
路由懒加载
路由懒加载是优化应用性能的重要手段,可以按需加载页面组件。
- const routes = [
- {
- path: '/about',
- name: 'About',
- component: () => import('../views/About.vue')
- }
- ]
复制代码
将同一模块的路由打包到同一个chunk中:
- const routes = [
- {
- path: '/user',
- name: 'User',
- component: () => import(/* webpackChunkName: "user" */ '../views/User.vue')
- },
- {
- path: '/user/list',
- name: 'UserList',
- component: () => import(/* webpackChunkName: "user" */ '../views/UserList.vue')
- }
- ]
复制代码
在用户可能访问某个路由前提前加载:
- // 在鼠标悬停时预加载
- <router-link to="/about" @mouseover="preloadAbout">About</router-link>
- function preloadAbout() {
- import('../views/About.vue')
- }
复制代码
路由过渡动画
路由过渡动画可以提升用户体验,使页面切换更加流畅。
- <template>
- <router-view v-slot="{ Component }">
- <transition name="fade" mode="out-in">
- <component :is="Component" />
- </transition>
- </router-view>
- </template>
- <style>
- .fade-enter-active,
- .fade-leave-active {
- transition: opacity 0.3s ease;
- }
- .fade-enter-from,
- .fade-leave-to {
- opacity: 0;
- }
- </style>
复制代码- <template>
- <router-view v-slot="{ Component, route }">
- <transition :name="route.meta.transition || 'fade'" mode="out-in">
- <component :is="Component" />
- </transition>
- </router-view>
- </template>
复制代码- <template>
- <router-view v-slot="{ Component, route }">
- <transition :name="getTransitionName(route)" mode="out-in">
- <keep-alive>
- <component :is="Component" />
- </keep-alive>
- </transition>
- </router-view>
- </template>
- <script>
- export default {
- methods: {
- getTransitionName(route) {
- // 根据路由深度或其他条件返回不同的过渡名称
- const depth = route.meta.depth || 0
- return depth % 2 === 0 ? 'slide-left' : 'slide-right'
- }
- }
- }
- </script>
复制代码
路由数据预取
在进入路由前预取数据可以提升用户体验,避免页面加载后再显示加载状态。
- // 在路由配置中定义数据预取函数
- const routes = [
- {
- path: '/user/:id',
- component: UserDetail,
- meta: {
- requiresData: true,
- dataPreFetch: (route) => {
- return fetchUserDetail(route.params.id)
- }
- }
- }
- ]
- // 在全局解析守卫中预取数据
- router.beforeResolve(async (to) => {
- if (to.meta.requiresData && to.meta.dataPreFetch) {
- try {
- // 预取数据并存储在全局状态中
- const data = await to.meta.dataPreFetch(to)
- store.commit('setPreFetchedData', { route: to.name, data })
- } catch (error) {
- // 处理错误
- console.error('Data pre-fetch failed:', error)
- return false // 取消导航
- }
- }
- })
复制代码- export default {
- async beforeRouteEnter(to, from, next) {
- try {
- const data = await fetchData(to.params.id)
- next(vm => {
- vm.setData(data)
- })
- } catch (error) {
- next('/error') // 跳转到错误页面
- }
- }
- }
复制代码- import { onBeforeRouteUpdate, useRoute } from 'vue-router'
- import { ref, watch } from 'vue'
- export default {
- setup() {
- const route = useRoute()
- const data = ref(null)
- const loading = ref(false)
- const error = ref(null)
-
- async function fetchData(id) {
- loading.value = true
- error.value = null
-
- try {
- data.value = await fetchUserData(id)
- } catch (err) {
- error.value = err
- } finally {
- loading.value = false
- }
- }
-
- // 初始加载
- fetchData(route.params.id)
-
- // 路由更新时重新获取数据
- onBeforeRouteUpdate(async (to) => {
- if (to.params.id !== route.params.id) {
- await fetchData(to.params.id)
- }
- })
-
- return {
- data,
- loading,
- error
- }
- }
- }
复制代码
实战案例:构建一个大型单页应用的路由架构
让我们通过一个实战案例来综合运用上述技术,构建一个大型单页应用的路由架构。
项目结构设计
- src/
- ├── api/ # API请求
- ├── assets/ # 静态资源
- ├── components/ # 公共组件
- ├── composables/ # 组合式函数
- ├── layouts/ # 布局组件
- │ ├── AuthLayout.vue # 认证布局
- │ └── MainLayout.vue # 主布局
- ├── router/ # 路由配置
- │ ├── guards.js # 路由守卫
- │ ├── modules/ # 路由模块
- │ │ ├── dashboard.js
- │ │ ├── user.js
- │ │ ├── product.js
- │ │ └── settings.js
- │ └── index.js # 路主路由配置
- ├── stores/ # Pinia状态管理
- ├── utils/ # 工具函数
- └── views/ # 页面组件
- ├── dashboard/
- ├── user/
- ├── product/
- ├── settings/
- └── error/
复制代码
路由模块划分
- // src/router/index.js
- import { createRouter, createWebHistory } from 'vue-router'
- import setupGuards from './guards'
- // 基础路由
- const routes = [
- {
- path: '/login',
- name: 'Login',
- component: () => import('@/views/login/index.vue'),
- meta: {
- title: '登录',
- layout: 'AuthLayout'
- }
- },
- {
- path: '/404',
- name: '404',
- component: () => import('@/views/error/404.vue'),
- meta: {
- title: '页面不存在',
- layout: 'MainLayout'
- }
- },
- {
- path: '/403',
- name: '403',
- component: () => import('@/views/error/403.vue'),
- meta: {
- title: '无权限访问',
- layout: 'MainLayout'
- }
- },
- {
- path: '/:pathMatch(.*)*',
- redirect: '/404'
- }
- ]
- const router = createRouter({
- history: createWebHistory(),
- routes
- })
- // 设置路由守卫
- setupGuards(router)
- export default router
复制代码- // src/router/modules/dashboard.js
- export default {
- path: '/',
- name: 'Dashboard',
- component: () => import('@/layouts/MainLayout.vue'),
- redirect: '/dashboard',
- meta: {
- title: '控制台',
- icon: 'dashboard',
- requiresAuth: true
- },
- children: [
- {
- path: 'dashboard',
- name: 'DashboardHome',
- component: () => import('@/views/dashboard/index.vue'),
- meta: {
- title: '首页',
- icon: 'home',
- affix: true // 固定标签页
- }
- },
- {
- path: 'workbench',
- name: 'Workbench',
- component: () => import('@/views/dashboard/workbench.vue'),
- meta: {
- title: '工作台',
- icon: 'workbench'
- }
- },
- {
- path: 'analysis',
- name: 'Analysis',
- component: () => import('@/views/dashboard/analysis.vue'),
- meta: {
- title: '数据分析',
- icon: 'chart',
- keepAlive: true // 缓存页面
- }
- }
- ]
- }
- // src/router/modules/user.js
- export default {
- path: '/user',
- name: 'User',
- component: () => import('@/layouts/MainLayout.vue'),
- redirect: '/user/list',
- meta: {
- title: '用户管理',
- icon: 'user',
- requiresAuth: true,
- permission: 'user' // 需要user权限
- },
- children: [
- {
- path: 'list',
- name: 'UserList',
- component: () => import('@/views/user/list.vue'),
- meta: {
- title: '用户列表',
- icon: 'list'
- }
- },
- {
- path: 'detail/:id',
- name: 'UserDetail',
- component: () => import('@/views/user/detail.vue'),
- props: true,
- meta: {
- title: '用户详情',
- activeMenu: '/user/list' // 高亮菜单
- }
- },
- {
- path: 'create',
- name: 'UserCreate',
- component: () => import('@/views/user/create.vue'),
- meta: {
- title: '新建用户',
- activeMenu: '/user/list',
- hidden: true // 隐藏菜单
- }
- }
- ]
- }
复制代码
路由守卫实现
- // src/router/guards.js
- import { useAuthStore } from '@/stores/auth'
- import { usePermissionStore } from '@/stores/permission'
- export default function setupGuards(router) {
- // 全局前置守卫
- router.beforeEach(async (to, from, next) => {
- // 显示加载条
- window.$loadingBar?.start()
-
- // 设置页面标题
- document.title = to.meta.title ? `${to.meta.title} - 应用名称` : '应用名称'
-
- const authStore = useAuthStore()
- const permissionStore = usePermissionStore()
-
- // 检查是否需要登录
- if (to.meta.requiresAuth && !authStore.isAuthenticated) {
- next({
- path: '/login',
- query: { redirect: to.fullPath }
- })
- return
- }
-
- // 已登录但访问登录页,重定向到首页
- if (to.path === '/login' && authStore.isAuthenticated) {
- next({ path: '/' })
- return
- }
-
- // 检查权限
- if (to.meta.permission && !permissionStore.hasPermission(to.meta.permission)) {
- next({ path: '/403' })
- return
- }
-
- // 动态添加路由
- if (authStore.isAuthenticated && !permissionStore.isRoutesAdded) {
- try {
- await permissionStore.generateRoutes()
- permissionStore.routes.forEach(route => {
- router.addRoute(route)
- })
- permissionStore.setRoutesAdded(true)
-
- // 确保addRoutes已完成
- next({ ...to, replace: true })
- return
- } catch (error) {
- console.error('添加路由失败:', error)
- next('/login')
- return
- }
- }
-
- next()
- })
-
- // 全局解析守卫
- router.beforeResolve(async (to) => {
- // 预取数据
- if (to.meta.dataPreFetch) {
- try {
- await to.meta.dataPreFetch(to)
- } catch (error) {
- console.error('数据预取失败:', error)
- return false
- }
- }
- })
-
- // 全局后置钩子
- router.afterEach(() => {
- // 结束加载条
- window.$loadingBar?.finish()
- })
-
- // 路由错误处理
- router.onError((error) => {
- console.error('路由错误:', error)
- window.$loadingBar?.error()
- })
- }
复制代码
权限控制实现
- // src/stores/permission.js
- import { defineStore } from 'pinia'
- import { constantRoutes, asyncRoutes } from '@/router'
- import { useAuthStore } from './auth'
- export const usePermissionStore = defineStore('permission', {
- state: () => ({
- routes: [],
- addRoutes: [],
- isRoutesAdded: false,
- permissions: []
- }),
-
- getters: {
- hasPermission: (state) => (permission) => {
- return state.permissions.includes(permission)
- },
-
- menuRoutes: (state) => {
- return state.routes.filter(route => {
- return !route.meta?.hidden
- })
- }
- },
-
- actions: {
- setRoutes(routes) {
- this.addRoutes = routes
- this.routes = constantRoutes.concat(routes)
- },
-
- setRoutesAdded(added) {
- this.isRoutesAdded = added
- },
-
- setPermissions(permissions) {
- this.permissions = permissions
- },
-
- async generateRoutes() {
- const authStore = useAuthStore()
-
- // 从后端获取用户权限
- const { permissions } = await authStore.fetchUserPermissions()
- this.setPermissions(permissions)
-
- // 根据权限过滤路由
- const accessedRoutes = filterAsyncRoutes(asyncRoutes, permissions)
-
- this.setRoutes(accessedRoutes)
- return accessedRoutes
- }
- }
- })
- // 根据权限过滤路由
- function filterAsyncRoutes(routes, permissions) {
- const res = []
-
- routes.forEach(route => {
- const tmp = { ...route }
-
- if (hasPermission(permissions, tmp)) {
- if (tmp.children) {
- tmp.children = filterAsyncRoutes(tmp.children, permissions)
- }
- res.push(tmp)
- }
- })
-
- return res
- }
- // 检查路由权限
- function hasPermission(permissions, route) {
- if (route.meta && route.meta.permission) {
- return permissions.includes(route.meta.permission)
- } else {
- return true
- }
- }
复制代码
性能优化策略
- // 使用webpackChunkName进行代码分割
- const routes = [
- {
- path: '/dashboard',
- name: 'Dashboard',
- component: () => import(/* webpackChunkName: "dashboard" */ '@/views/dashboard/index.vue')
- },
- {
- path: '/user',
- name: 'User',
- component: () => import(/* webpackChunkName: "user" */ '@/views/user/index.vue'),
- children: [
- {
- path: 'list',
- name: 'UserList',
- component: () => import(/* webpackChunkName: "user" */ '@/views/user/list.vue')
- },
- {
- path: 'detail/:id',
- name: 'UserDetail',
- component: () => import(/* webpackChunkName: "user" */ '@/views/user/detail.vue')
- }
- ]
- }
- ]
复制代码- // 在应用初始化后预加载关键路由
- import router from '@/router'
- function preloadCriticalRoutes() {
- // 预加载用户可能访问的路由
- const criticalRoutes = ['/dashboard', '/user/list']
-
- criticalRoutes.forEach(path => {
- const route = router.resolve(path)
- if (route.matched.length) {
- route.matched.forEach(record => {
- if (record.components.default) {
- record.components.default()
- }
- })
- }
- })
- }
- // 在应用挂载后调用
- app.mount('#app')
- preloadCriticalRoutes()
复制代码- // 使用keep-alive缓存组件
- <template>
- <router-view v-slot="{ Component, route }">
- <keep-alive :include="cachedViews">
- <component :is="Component" :key="route.fullPath" />
- </keep-alive>
- </router-view>
- </template>
- <script>
- export default {
- data() {
- return {
- cachedViews: ['DashboardHome', 'Analysis'] // 需要缓存的组件名称
- }
- },
- watch: {
- $route(to) {
- if (to.meta.keepAlive) {
- if (!this.cachedViews.includes(to.name)) {
- this.cachedViews.push(to.name)
- }
- }
- }
- }
- }
- </script>
复制代码
最佳实践和常见问题解决
路由设计最佳实践
1. 扁平化路由结构:尽量保持路由结构扁平,避免过深的嵌套。
2. 一致的命名约定:使用一致的命名约定,如使用kebab-case命名路由路径,使用PascalCase命名组件。
3. 合理使用路由元信息:将路由相关的配置信息放在meta字段中,如权限、标题、缓存等。
4. 模块化路由:将路由按功能模块划分,提高可维护性。
5. 懒加载:所有非首屏路由都应使用懒加载,减少初始加载体积。
扁平化路由结构:尽量保持路由结构扁平,避免过深的嵌套。
一致的命名约定:使用一致的命名约定,如使用kebab-case命名路由路径,使用PascalCase命名组件。
合理使用路由元信息:将路由相关的配置信息放在meta字段中,如权限、标题、缓存等。
模块化路由:将路由按功能模块划分,提高可维护性。
懒加载:所有非首屏路由都应使用懒加载,减少初始加载体积。
常见问题解决
- // 问题:路由参数变化但组件复用,导致数据不更新
- // 解决方案1:监听路由参数变化
- export default {
- setup() {
- const route = useRoute()
-
- watch(() => route.params.id, (newId) => {
- fetchData(newId)
- }, { immediate: true })
- }
- }
- // 解决方案2:使用key强制刷新组件
- <router-view :key="$route.fullPath" />
复制代码- // 问题:动态添加路由后,立即导航到新路由不生效
- // 解决方案:使用next({ ...to, replace: true })重新导航
- router.beforeEach(async (to, from, next) => {
- if (needsToAddRoutes) {
- await addRoutes()
- next({ ...to, replace: true })
- } else {
- next()
- }
- })
复制代码- // 问题:在路由守卫中执行异步操作,导致导航行为异常
- // 解决方案:确保在所有异步操作完成后再调用next
- router.beforeEach(async (to, from, next) => {
- try {
- await checkAuth()
- await fetchPermissions()
- next()
- } catch (error) {
- next('/login')
- }
- })
复制代码- // 问题:路由切换时滚动位置不符合预期
- // 解决方案:配置路由滚动行为
- const router = createRouter({
- history: createWebHistory(),
- routes,
- scrollBehavior(to, from, savedPosition) {
- if (savedPosition) {
- // 使用浏览器的前进/后退按钮时恢复滚动位置
- return savedPosition
- } else if (to.hash) {
- // 如果有锚点,滚动到锚点位置
- return {
- el: to.hash,
- behavior: 'smooth'
- }
- } else {
- // 否则滚动到页面顶部
- return { top: 0 }
- }
- }
- })
复制代码
总结
Vue3和Vue Router为我们提供了构建现代单页应用的强大能力。通过本文的介绍,我们深入了解了Vue Router的高级特性,包括动态路由、嵌套路由、导航守卫等,并学习了如何构建可扩展、高性能的前端路由架构。
在实际项目中,我们应该根据应用规模和需求选择合适的路由设计方案,遵循模块化、可扩展的原则,合理使用路由懒加载、权限控制、数据预取等技术,以提供更好的用户体验和开发体验。
随着Vue3和Vue Router的不断发展,我们还需要持续关注新特性和最佳实践,不断优化我们的路由架构,以适应不断变化的前端开发需求。
希望本文能够帮助你深入理解Vue3 Vue Router的高级用法,并在实际项目中构建出可扩展、高性能的前端路由架构。 |
|