Vue.js嵌套路由

嵌套路由是 Vue Router 中一个非常强大的功能,它允许我们在组件内部嵌套其他路由,从而构建复杂的页面布局和导航结构。通过嵌套路由,我们可以创建多级的用户界面,每个级别都有自己的路由配置和组件。

嵌套路由概述

嵌套路由的核心概念是在一个路由组件内部定义子路由。这使得我们可以:

  • 创建具有层级结构的页面布局
  • 在父组件中保持公共部分(如导航栏、侧边栏)
  • 实现标签页、抽屉式导航等复杂界面
  • 按模块组织路由配置,提高可维护性
  • 实现动态的嵌套路由结构
核心概念

嵌套路由通过 children 属性在路由配置中定义。父路由的组件模板中需要包含 <router-view> 元素来渲染子路由组件。

为什么需要嵌套路由?

考虑一个典型的网站布局:

App.vue
主布局(包含顶部导航)
DashboardLayout.vue
侧边栏导航
DashboardHome.vue
主内容区域(嵌套路由出口)

使用嵌套路由,我们可以:

  1. 保持顶部导航栏和页脚在页面切换时不变
  2. 在侧边栏中根据当前路由高亮对应的菜单项
  3. 在内容区域根据子路由显示不同的组件
  4. 实现面包屑导航,展示当前页面的层级路径

基本嵌套路由配置

路由配置结构

示例基本的嵌套路由配置

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/dashboard',
    name: 'dashboard',
    component: () => import('@/views/DashboardLayout.vue'),
    // children 属性定义嵌套路由
    children: [
      // 当访问 /dashboard 时,默认显示的子路由
      {
        path: '', // 空路径,匹配父路由本身
        name: 'dashboard.home',
        component: () => import('@/views/dashboard/DashboardHome.vue')
      },
      // 当访问 /dashboard/profile 时显示
      {
        path: 'profile', // 相对路径,相对于父路由的路径
        name: 'dashboard.profile',
        component: () => import('@/views/dashboard/Profile.vue')
      },
      // 当访问 /dashboard/settings 时显示
      {
        path: 'settings',
        name: 'dashboard.settings',
        component: () => import('@/views/dashboard/Settings.vue')
      }
    ]
  }
]

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

export default router
                        
路由结构树
/dashboard
├── DashboardLayout.vue
├── /dashboard (空路径)
│ └── DashboardHome.vue
├── /dashboard/profile
│ └── Profile.vue
└── /dashboard/settings
└── Settings.vue

组件结构

示例父组件模板(DashboardLayout.vue)

<!-- DashboardLayout.vue -->
<template>
  <div class="dashboard-layout">
    <!-- 顶部导航栏 -->
    <header class="dashboard-header">
      <h2>控制面板</h2>
      <nav>
        <router-link to="/dashboard">首页</router-link>
        <router-link to="/dashboard/profile">个人资料</router-link>
        <router-link to="/dashboard/settings">设置</router-link>
      </nav>
    </header>

    <!-- 内容区域 -->
    <main class="dashboard-content">
      <!-- 嵌套路由出口 -->
      <router-view></router-view>
    </main>
  </div>
</template>

<script>
export default {
  name: 'DashboardLayout'
}
</script>

<style scoped>
.dashboard-layout {
  display: flex;
  flex-direction: column;
  height: 100vh;
}

.dashboard-header {
  background-color: #35495e;
  color: white;
  padding: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.dashboard-header nav a {
  color: white;
  margin-left: 20px;
  text-decoration: none;
}

.dashboard-header nav a.router-link-active {
  color: #42b983;
  font-weight: bold;
}

.dashboard-content {
  flex: 1;
  padding: 20px;
}
</style>
                        
示例子组件示例(Profile.vue)

<!-- Profile.vue -->
<template>
  <div class="profile-page">
    <h3>个人资料</h3>
    <div class="profile-content">
      <p>这里是个人资料页面内容。</p>
      <p>这个组件在 DashboardLayout 的 router-view 中渲染。</p>
    </div>
  </div>
</template>

<script>
export default {
  name: 'ProfilePage'
}
</script>

<style scoped>
.profile-page {
  padding: 20px;
}

.profile-content {
  margin-top: 20px;
  padding: 20px;
  background-color: #f5f5f5;
  border-radius: 8px;
}
</style>
                        

多级嵌套路由

嵌套路由可以多层嵌套,构建更复杂的页面结构。

示例多级嵌套路由配置

const routes = [
  {
    path: '/admin',
    component: () => import('@/views/AdminLayout.vue'),
    meta: { requiresAuth: true, role: 'admin' },
    children: [
      {
        path: '',
        name: 'admin.dashboard',
        component: () => import('@/views/admin/Dashboard.vue')
      },
      {
        path: 'users',
        component: () => import('@/views/admin/UsersLayout.vue'),
        children: [
          {
            path: '',
            name: 'admin.users',
            component: () => import('@/views/admin/users/List.vue')
          },
          {
            path: 'create',
            name: 'admin.users.create',
            component: () => import('@/views/admin/users/Create.vue')
          },
          {
            path: ':id',
            name: 'admin.users.detail',
            component: () => import('@/views/admin/users/Detail.vue'),
            children: [
              {
                path: '',
                name: 'admin.users.detail.info',
                component: () => import('@/views/admin/users/detail/Info.vue')
              },
              {
                path: 'edit',
                name: 'admin.users.detail.edit',
                component: () => import('@/views/admin/users/detail/Edit.vue')
              },
              {
                path: 'permissions',
                name: 'admin.users.detail.permissions',
                component: () => import('@/views/admin/users/detail/Permissions.vue')
              }
            ]
          }
        ]
      },
      {
        path: 'products',
        component: () => import('@/views/admin/ProductsLayout.vue'),
        children: [
          {
            path: '',
            name: 'admin.products',
            component: () => import('@/views/admin/products/List.vue')
          },
          {
            path: 'categories',
            name: 'admin.products.categories',
            component: () => import('@/views/admin/products/Categories.vue')
          }
        ]
      }
    ]
  }
]
                        
多级嵌套路由结构
/admin
├── AdminLayout.vue
├── /admin
│ └── Dashboard.vue
├── /admin/users
│ ├── UsersLayout.vue
│ ├── /admin/users
│ │ └── List.vue
│ ├── /admin/users/create
│ │ └── Create.vue
│ └── /admin/users/:id
│ ├── Detail.vue
│ ├── /admin/users/:id
│ │ └── Info.vue
│ ├── /admin/users/:id/edit
│ │ └── Edit.vue
│ └── /admin/users/:id/permissions
│ └── Permissions.vue
└── /admin/products
├── ProductsLayout.vue
├── /admin/products
│ └── List.vue
└── /admin/products/categories
└── Categories.vue

嵌套路由导航

在嵌套路由中进行导航时,需要注意路径的写法。

导航到嵌套路由

示例导航到嵌套路由的各种方式

<!-- 在模板中导航 -->
<!-- 方法1: 使用完整路径 -->
<router-link to="/dashboard/profile">个人资料</router-link>

<!-- 方法2: 使用命名路由 -->
<router-link :to="{ name: 'dashboard.profile' }">个人资料</router-link>

<!-- 方法3: 相对路径(相对于当前路由) -->
<router-link :to="{ path: 'profile' }">个人资料</router-link>

<!-- 在 JavaScript 中导航 -->
<script>
// Options API
export default {
  methods: {
    goToProfile() {
      // 方法1: 使用完整路径
      this.$router.push('/dashboard/profile')

      // 方法2: 使用命名路由
      this.$router.push({ name: 'dashboard.profile' })

      // 方法3: 相对路径
      this.$router.push({ path: 'profile' })

      // 方法4: 带参数的嵌套路由
      this.$router.push({
        name: 'admin.users.detail',
        params: { id: 123 }
      })

      // 方法5: 带查询参数的嵌套路由
      this.$router.push({
        name: 'dashboard.profile',
        query: { tab: 'settings' }
      })
    }
  }
}
</script>

<!-- Composition API -->
<script setup>
import { useRouter } from 'vue-router'

const router = useRouter()

function goToProfile() {
  router.push('/dashboard/profile')
  // 或使用命名路由
  router.push({ name: 'dashboard.profile' })
}
</script>
                        

访问嵌套路由信息

示例在组件中访问嵌套路由信息

<template>
  <div>
    <h3>当前路由信息</h3>
    <!-- 显示当前路由的层级路径 -->
    <div v-if="$route.matched.length">
      <h4>路由匹配链:</h4>
      <ul>
        <li v-for="(route, index) in $route.matched" :key="index">
          {{ route.path }} - {{ route.name || '未命名' }}
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
// Options API
export default {
  computed: {
    // 获取当前路由的所有父路由
    parentRoutes() {
      return this.$route.matched.slice(0, -1)
    },

    // 获取当前路由
    currentRoute() {
      return this.$route.matched[this.$route.matched.length - 1]
    },

    // 面包屑导航数据
    breadcrumbs() {
      return this.$route.matched.map((route, index) => ({
        name: route.meta?.breadcrumb || route.name,
        path: route.path,
        isLast: index === this.$route.matched.length - 1
      }))
    }
  },

  created() {
    // 访问嵌套路由信息
    console.log('当前路由:', this.$route)
    console.log('匹配的路由记录:', this.$route.matched)
    console.log('父路由:', this.parentRoutes)
    console.log('当前路由记录:', this.currentRoute)
  }
}
</script>

<!-- Composition API -->
<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()

// 获取匹配的路由记录
const matchedRoutes = computed(() => route.matched)
const parentRoutes = computed(() => route.matched.slice(0, -1))
const currentRoute = computed(() => route.matched[route.matched.length - 1])

// 面包屑导航
const breadcrumbs = computed(() =>
  route.matched.map((routeRecord, index) => ({
    name: routeRecord.meta?.breadcrumb || routeRecord.name,
    path: routeRecord.path,
    isLast: index === route.matched.length - 1
  }))
)

console.log('当前路由:', route)
console.log('匹配的路由记录:', matchedRoutes.value)
</script>
                        

动态嵌套路由

嵌套路由也支持动态路径参数,可以构建更灵活的嵌套结构。

示例动态嵌套路由配置

const routes = [
  {
    path: '/projects',
    name: 'projects',
    component: () => import('@/views/ProjectsLayout.vue'),
    children: [
      {
        path: '',
        name: 'projects.list',
        component: () => import('@/views/projects/List.vue')
      },
      {
        path: ':projectId',
        name: 'projects.detail',
        component: () => import('@/views/projects/DetailLayout.vue'),
        props: true,
        children: [
          {
            path: '',
            name: 'projects.detail.overview',
            component: () => import('@/views/projects/detail/Overview.vue')
          },
          {
            path: 'tasks',
            name: 'projects.detail.tasks',
            component: () => import('@/views/projects/detail/Tasks.vue'),
            children: [
              {
                path: '',
                name: 'projects.detail.tasks.list',
                component: () => import('@/views/projects/detail/tasks/List.vue')
              },
              {
                path: ':taskId',
                name: 'projects.detail.tasks.detail',
                component: () => import('@/views/projects/detail/tasks/Detail.vue'),
                props: true
              }
            ]
          },
          {
            path: 'members',
            name: 'projects.detail.members',
            component: () => import('@/views/projects/detail/Members.vue')
          },
          {
            path: 'settings',
            name: 'projects.detail.settings',
            component: () => import('@/views/projects/detail/Settings.vue')
          }
        ]
      }
    ]
  }
]
                        
示例访问动态嵌套路由参数

<!-- 在嵌套的组件中访问参数 -->
<!-- Tasks.vue (第二级嵌套) -->
<template>
  <div>
    <h3>项目任务</h3>
    <p>当前项目ID: {{ projectId }}</p>

    <!-- 嵌套路由出口 -->
    <router-view></router-view>
  </div>
</template>

<script>
// Options API
export default {
  name: 'ProjectTasks',
  computed: {
    projectId() {
      return this.$route.params.projectId
    }
  }
}
</script>

<!-- TaskDetail.vue (第三级嵌套) -->
<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()

// 访问多级参数
const projectId = computed(() => route.params.projectId)
const taskId = computed(() => route.params.taskId)

console.log('项目ID:', projectId.value)
console.log('任务ID:', taskId.value)
console.log('所有参数:', route.params)
</script>
                        

嵌套路由演示

嵌套路由交互演示

模拟一个控制面板的嵌套路由结构:

控制面板首页

欢迎来到控制面板!请从左侧选择要查看的内容。

当前路由信息:

路径: /dashboard

匹配的路由记录: 1 个

点击左侧菜单模拟嵌套路由导航

嵌套路由与命名视图

嵌套路由可以与命名视图结合,创建更复杂的布局结构。

示例嵌套路由中的命名视图

const routes = [
  {
    path: '/app',
    component: () => import('@/views/AppLayout.vue'),
    children: [
      {
        path: 'dashboard',
        components: {
          default: () => import('@/views/dashboard/MainContent.vue'),
          sidebar: () => import('@/views/dashboard/Sidebar.vue'),
          header: () => import('@/views/dashboard/Header.vue')
        },
        children: [
          {
            path: '',
            components: {
              default: () => import('@/views/dashboard/Home.vue'),
              sidebar: () => import('@/views/dashboard/HomeSidebar.vue')
            }
          },
          {
            path: 'analytics',
            components: {
              default: () => import('@/views/dashboard/Analytics.vue'),
              sidebar: () => import('@/views/dashboard/AnalyticsSidebar.vue')
            }
          }
        ]
      }
    ]
  }
]
                        
示例多层命名视图模板

<!-- AppLayout.vue -->
<template>
  <div class="app-layout">
    <router-view name="header"></router-view>
    <div class="app-content">
      <router-view name="sidebar"></router-view>
      <main class="main-content">
        <router-view></router-view>
      </main>
    </div>
  </div>
</template>

<!-- DashboardSidebar.vue -->
<template>
  <aside class="dashboard-sidebar">
    <!-- 仪表板级别的侧边栏 -->
    <nav>
      <router-link to="/app/dashboard">首页</router-link>
      <router-link to="/app/dashboard/analytics">分析</router-link>
    </nav>

    <!-- 子路由的命名视图出口 -->
    <router-view name="sidebar"></router-view>
  </aside>
</template>
                        

嵌套路由的重定向和别名

重定向

示例嵌套路由的重定向配置

const routes = [
  {
    path: '/admin',
    redirect: '/admin/dashboard', // 重定向到子路由
    component: () => import('@/views/AdminLayout.vue'),
    children: [
      {
        path: 'dashboard',
        name: 'admin.dashboard',
        component: () => import('@/views/admin/Dashboard.vue')
      },
      {
        path: 'users',
        redirect: 'users/list', // 相对重定向
        component: () => import('@/views/admin/UsersLayout.vue'),
        children: [
          {
            path: 'list',
            name: 'admin.users.list',
            component: () => import('@/views/admin/users/List.vue')
          },
          {
            path: ':id',
            redirect: { name: 'admin.users.detail.info' }, // 命名路由重定向
            component: () => import('@/views/admin/users/DetailLayout.vue'),
            children: [
              {
                path: 'info',
                name: 'admin.users.detail.info',
                component: () => import('@/views/admin/users/detail/Info.vue')
              },
              {
                path: 'edit',
                name: 'admin.users.detail.edit',
                component: () => import('@/views/admin/users/detail/Edit.vue')
              }
            ]
          }
        ]
      }
    ]
  }
]
                        

别名

示例嵌套路由的别名配置

const routes = [
  {
    path: '/user',
    component: () => import('@/views/UserLayout.vue'),
    alias: '/member', // 父路由别名
    children: [
      {
        path: 'profile',
        name: 'user.profile',
        component: () => import('@/views/user/Profile.vue'),
        alias: ['/profile', '/account'] // 子路由别名
      },
      {
        path: 'settings',
        name: 'user.settings',
        component: () => import('@/views/user/Settings.vue'),
        alias: '/preferences' // 单个别名
      }
    ]
  }
]

// 访问以下路径都会显示 UserLayout 组件:
// /user
// /member (别名)

// 访问以下路径都会显示 Profile 组件:
// /user/profile
// /profile (别名)
// /account (别名)
                        

嵌套路由守卫

嵌套路由支持在各级路由上配置导航守卫。

示例嵌套路由的守卫配置

const routes = [
  {
    path: '/admin',
    component: () => import('@/views/AdminLayout.vue'),
    meta: { requiresAuth: true, role: 'admin' },
    // 父路由守卫
    beforeEnter: (to, from, next) => {
      console.log('进入管理后台')
      // 检查管理员权限
      if (!hasAdminRole()) {
        next({ name: 'forbidden' })
        return
      }
      next()
    },
    children: [
      {
        path: 'dashboard',
        name: 'admin.dashboard',
        component: () => import('@/views/admin/Dashboard.vue')
      },
      {
        path: 'users',
        component: () => import('@/views/admin/UsersLayout.vue'),
        meta: { requiresSuperAdmin: true },
        // 子路由守卫
        beforeEnter: (to, from, next) => {
          console.log('进入用户管理')
          // 检查超级管理员权限
          if (!hasSuperAdminRole()) {
            next({ name: 'admin.dashboard' })
            return
          }
          next()
        },
        children: [
          {
            path: '',
            name: 'admin.users',
            component: () => import('@/views/admin/users/List.vue'),
            // 更深层的守卫
            beforeEnter: (to, from, next) => {
              console.log('进入用户列表')
              // 额外的检查
              next()
            }
          }
        ]
      }
    ]
  }
]

// 全局守卫也会触发
router.beforeEach((to, from, next) => {
  console.log('全局守卫:', to.path)
  next()
})

// 组件内守卫也会按照嵌套层级执行
// 父组件的 beforeRouteLeave → 全局 beforeEach → 子组件的 beforeRouteEnter
                        

嵌套路由最佳实践

嵌套路由最佳实践
  • 合理规划层级:避免过深的嵌套(建议不超过3-4层)
  • 使用命名路由:提高代码可读性和可维护性
  • 模块化配置:按功能模块拆分路由配置
  • 空路径子路由:为父路由设置默认显示的子路由
  • 使用面包屑:通过 $route.matched 实现面包屑导航
  • 守卫分层:根据需要在不同层级设置导航守卫
  • 动态导入:使用懒加载提高应用性能
  • 保持一致:保持路由命名和路径的命名规范一致
常见问题
  • 路径冲突:注意绝对路径和相对路径的区别
  • 无限循环:避免在重定向中产生循环
  • 参数传递:在多层嵌套中正确传递和访问参数
  • 组件复用:同一组件在不同嵌套层级使用时注意上下文差异
  • 守卫执行顺序:理解嵌套路由中守卫的执行顺序

总结

嵌套路由是 Vue Router 中构建复杂单页面应用的重要工具。通过合理的嵌套路由设计,我们可以:

  • 创建具有层级结构的页面布局
  • 保持公共UI元素在页面切换时不变
  • 实现模块化的路由配置和代码组织
  • 构建动态的、可扩展的应用程序结构
  • 实现面包屑导航和层级导航菜单

掌握嵌套路由的使用技巧,能够让你在开发复杂的 Vue.js 应用时更加得心应手。记住关键概念:

  • children 属性:定义嵌套路由配置
  • router-view:在父组件中渲染子路由组件
  • $route.matched:访问当前路由的匹配链
  • 命名路由:简化嵌套路由的导航
  • 动态导入:提高嵌套路由的性能

测试你的理解

思考题
  1. 如何在路由配置中定义嵌套路由?
  2. 在父组件模板中,哪里应该放置 <router-view>
  3. 如何访问当前路由的所有父级路由信息?
  4. 嵌套路由中,绝对路径和相对路径有什么区别?
  5. 如何在多层嵌套路由中传递和访问动态参数?