Skip to content

嵌套路由 | Vue Router 多层级路由完全指南 🏗️

构建复杂应用的必备技能!掌握嵌套路由,轻松实现多层级页面结构。

🌟 什么是嵌套路由?

嵌套路由是 Vue Router 的强大功能,它允许你构建多层级的页面结构。想象一下现代应用的复杂界面:用户中心包含个人资料、订单历史、设置页面等子页面 - 这就是嵌套路由的典型应用场景!

💡 为什么需要嵌套路由?

现代 Web 应用通常具有复杂的页面层次结构。嵌套路由让你能够优雅地组织这些层次关系,实现代码的模块化和可维护性。

🎯 URL 结构与组件层次

让我们通过一个直观的例子来理解嵌套路由:

/user/john/profile                    /user/john/posts 
┌──────────────────┐                 ┌──────────────────┐
│ User             │                 │ User             │
│ ┌──────────────┐ │                 │ ┌──────────────┐ │
│ │ Profile      │ │  ────────────>  │ │ Posts        │ │
│ │              │ │                 │ │              │ │
│ └──────────────┘ │                 │ └──────────────┘ │
└──────────────────┘                 └──────────────────┘

这种结构体现了:

  • 🏠 外层容器:User 组件提供公共布局
  • 📄 内层内容:Profile/Posts 组件提供具体功能
  • 🔗 URL 层次:路径结构反映组件嵌套关系

🚀 基础用法 - 5分钟上手

📝 定义嵌套路由

javascript
import User from './components/User.vue'
import UserProfile from './components/UserProfile.vue'
import UserPosts from './components/UserPosts.vue'
import UserSettings from './components/UserSettings.vue'

const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      // 🎯 子路由配置
      {
        // 当 /user/:id/profile 匹配成功时
        // UserProfile 将被渲染到 User 的 <router-view> 内部
        path: 'profile',
        component: UserProfile,
        name: 'user-profile'
      },
      {
        // 当 /user/:id/posts 匹配成功时
        path: 'posts',
        component: UserPosts,
        name: 'user-posts'
      },
      {
        // 当 /user/:id/settings 匹配成功时
        path: 'settings',
        component: UserSettings,
        name: 'user-settings'
      }
    ]
  }
]

🎨 父组件模板

vue
<!-- User.vue -->
<template>
  <div class="user-layout">
    <!-- 🎯 公共头部 -->
    <header class="user-header">
      <div class="user-info">
        <img :src="userAvatar" :alt="userName" class="avatar" />
        <h1>{{ userName }}</h1>
        <p class="user-id">ID: {{ $route.params.id }}</p>
      </div>
      
      <!-- 🧭 导航菜单 -->
      <nav class="user-nav">
        <router-link 
          :to="{ name: 'user-profile', params: { id: $route.params.id } }"
          class="nav-link"
          active-class="active"
        >
          👤 个人资料
        </router-link>
        
        <router-link 
          :to="{ name: 'user-posts', params: { id: $route.params.id } }"
          class="nav-link"
          active-class="active"
        >
          📝 我的文章
        </router-link>
        
        <router-link 
          :to="{ name: 'user-settings', params: { id: $route.params.id } }"
          class="nav-link"
          active-class="active"
        >
          ⚙️ 设置
        </router-link>
      </nav>
    </header>
    
    <!-- 🎯 子路由渲染区域 -->
    <main class="user-content">
      <router-view />
    </main>
  </div>
</template>

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

const route = useRoute()

// 🎯 根据路由参数获取用户信息
const userId = computed(() => route.params.id)
const userName = computed(() => `用户 ${userId.value}`)
const userAvatar = computed(() => `https://api.dicebear.com/7.x/avataaars/svg?seed=${userId.value}`)
</script>

<style scoped>
.user-layout {
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
}

.user-header {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 24px;
  border-radius: 12px;
  margin-bottom: 24px;
}

.user-info {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 20px;
}

.avatar {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  border: 3px solid rgba(255, 255, 255, 0.3);
}

.user-nav {
  display: flex;
  gap: 16px;
}

.nav-link {
  padding: 8px 16px;
  border-radius: 8px;
  text-decoration: none;
  color: rgba(255, 255, 255, 0.8);
  transition: all 0.3s ease;
}

.nav-link:hover,
.nav-link.active {
  background: rgba(255, 255, 255, 0.2);
  color: white;
}

.user-content {
  background: white;
  border-radius: 12px;
  padding: 24px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
</style>

📄 子组件示例

vue
<!-- UserProfile.vue -->
<template>
  <div class="user-profile">
    <h2>👤 个人资料</h2>
    
    <div class="profile-grid">
      <div class="profile-card">
        <h3>📊 基本信息</h3>
        <div class="info-item">
          <span class="label">用户 ID:</span>
          <span class="value">{{ $route.params.id }}</span>
        </div>
        <div class="info-item">
          <span class="label">注册时间:</span>
          <span class="value">2023-01-15</span>
        </div>
        <div class="info-item">
          <span class="label">最后登录:</span>
          <span class="value">2024-01-15 14:30</span>
        </div>
      </div>
      
      <div class="profile-card">
        <h3>📈 统计数据</h3>
        <div class="stats-grid">
          <div class="stat-item">
            <div class="stat-number">156</div>
            <div class="stat-label">文章数量</div>
          </div>
          <div class="stat-item">
            <div class="stat-number">2.3k</div>
            <div class="stat-label">获赞数</div>
          </div>
          <div class="stat-item">
            <div class="stat-number">89</div>
            <div class="stat-label">关注者</div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
.profile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 24px;
  margin-top: 20px;
}

.profile-card {
  background: #f8fafc;
  border-radius: 8px;
  padding: 20px;
  border: 1px solid #e2e8f0;
}

.info-item {
  display: flex;
  justify-content: space-between;
  padding: 8px 0;
  border-bottom: 1px solid #e2e8f0;
}

.stats-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  margin-top: 16px;
}

.stat-item {
  text-align: center;
}

.stat-number {
  font-size: 24px;
  font-weight: bold;
  color: #3b82f6;
}

.stat-label {
  font-size: 14px;
  color: #64748b;
  margin-top: 4px;
}
</style>

🎯 默认子路由

当用户访问 /user/123 时,你可能希望显示默认内容。这时可以使用空路径的子路由:

javascript
const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      // 🎯 默认子路由 - 空路径
      { 
        path: '', 
        component: UserHome,
        name: 'user-home'
      },
      
      // 其他子路由
      { path: 'profile', component: UserProfile },
      { path: 'posts', component: UserPosts }
    ]
  }
]

🏠 默认页面组件

vue
<!-- UserHome.vue -->
<template>
  <div class="user-home">
    <div class="welcome-banner">
      <h2>🎉 欢迎回来!</h2>
      <p>这里是您的个人中心首页</p>
    </div>
    
    <div class="quick-actions">
      <h3>🚀 快速操作</h3>
      <div class="action-grid">
        <router-link 
          :to="{ name: 'user-profile', params: { id: $route.params.id } }"
          class="action-card"
        >
          <div class="action-icon">👤</div>
          <div class="action-title">查看资料</div>
          <div class="action-desc">管理个人信息</div>
        </router-link>
        
        <router-link 
          :to="{ name: 'user-posts', params: { id: $route.params.id } }"
          class="action-card"
        >
          <div class="action-icon">📝</div>
          <div class="action-title">我的文章</div>
          <div class="action-desc">查看发布的内容</div>
        </router-link>
        
        <router-link 
          :to="{ name: 'user-settings', params: { id: $route.params.id } }"
          class="action-card"
        >
          <div class="action-icon">⚙️</div>
          <div class="action-title">账户设置</div>
          <div class="action-desc">个性化配置</div>
        </router-link>
      </div>
    </div>
    
    <!-- 🎯 最近活动 -->
    <div class="recent-activity">
      <h3>📊 最近活动</h3>
      <div class="activity-list">
        <div class="activity-item">
          <span class="activity-time">2小时前</span>
          <span class="activity-text">发布了新文章《Vue Router 进阶指南》</span>
        </div>
        <div class="activity-item">
          <span class="activity-time">1天前</span>
          <span class="activity-text">更新了个人资料</span>
        </div>
        <div class="activity-item">
          <span class="activity-time">3天前</span>
          <span class="activity-text">获得了 50 个赞</span>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
.welcome-banner {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 32px;
  border-radius: 12px;
  text-align: center;
  margin-bottom: 32px;
}

.action-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 20px;
  margin-top: 16px;
}

.action-card {
  background: white;
  border: 2px solid #e2e8f0;
  border-radius: 12px;
  padding: 24px;
  text-align: center;
  text-decoration: none;
  color: inherit;
  transition: all 0.3s ease;
}

.action-card:hover {
  border-color: #3b82f6;
  transform: translateY(-2px);
  box-shadow: 0 8px 25px rgba(59, 130, 246, 0.15);
}

.action-icon {
  font-size: 32px;
  margin-bottom: 12px;
}

.action-title {
  font-weight: bold;
  margin-bottom: 8px;
}

.action-desc {
  color: #64748b;
  font-size: 14px;
}

.activity-list {
  margin-top: 16px;
}

.activity-item {
  display: flex;
  justify-content: space-between;
  padding: 12px 0;
  border-bottom: 1px solid #e2e8f0;
}

.activity-time {
  color: #64748b;
  font-size: 14px;
  min-width: 80px;
}
</style>

🎯 嵌套的命名路由

在处理嵌套路由时,通常给子路由命名以便于导航:

javascript
const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      // 🎯 给子路由命名
      { 
        path: '', 
        name: 'user', 
        component: UserHome 
      },
      { 
        path: 'profile', 
        name: 'user-profile', 
        component: UserProfile 
      },
      { 
        path: 'posts', 
        name: 'user-posts', 
        component: UserPosts 
      }
    ]
  }
]

🧭 编程式导航

vue
<script setup>
import { useRouter, useRoute } from 'vue-router'

const router = useRouter()
const route = useRoute()

// 🎯 导航到用户资料页
function goToProfile() {
  router.push({
    name: 'user-profile',
    params: { id: route.params.id }
  })
}

// 🔄 导航到其他用户的页面
function goToUser(userId) {
  router.push({
    name: 'user',
    params: { id: userId }
  })
}

// 📝 导航到文章页面并传递查询参数
function goToPosts(filter = 'all') {
  router.push({
    name: 'user-posts',
    params: { id: route.params.id },
    query: { filter }
  })
}
</script>

🏗️ 深层嵌套路由

Vue Router 支持任意深度的路由嵌套:

javascript
const routes = [
  {
    path: '/admin',
    component: AdminLayout,
    children: [
      {
        path: 'users',
        component: UserManagement,
        children: [
          {
            path: ':id',
            component: UserDetail,
            children: [
              { path: 'profile', component: UserProfileEdit },
              { path: 'permissions', component: UserPermissions },
              { path: 'activity', component: UserActivity }
            ]
          }
        ]
      }
    ]
  }
]

这将创建如下的 URL 结构:

  • /admin/users - 用户管理页面
  • /admin/users/123 - 用户详情页面
  • /admin/users/123/profile - 用户资料编辑
  • /admin/users/123/permissions - 用户权限管理
  • /admin/users/123/activity - 用户活动记录

🎯 忽略父组件(4.1+)

有时你只想利用路由的层次关系,但不需要嵌套组件。可以省略父路由的 component 选项:

javascript
const routes = [
  {
    path: '/admin',
    // 🎯 没有指定 component,跳过父级组件
    children: [
      { path: '', component: AdminOverview },
      { path: 'users', component: AdminUserList },
      { path: 'users/:id', component: AdminUserDetails },
      { path: 'settings', component: AdminSettings }
    ]
  }
]

这种配置下:

  • 顶级 <router-view> 会跳过父级,直接渲染子路由组件
  • 路由仍然保持层次结构,便于管理和权限控制
  • 适用于路由分组和高级功能(如路由守卫、元信息)

🎨 实战案例

🛍️ 电商后台管理系统

javascript
const routes = [
  {
    path: '/admin',
    component: AdminLayout,
    meta: { requiresAuth: true, role: 'admin' },
    children: [
      // 📊 仪表盘
      { 
        path: '', 
        name: 'admin-dashboard', 
        component: Dashboard 
      },
      
      // 🛍️ 商品管理
      {
        path: 'products',
        component: ProductManagement,
        children: [
          { path: '', name: 'product-list', component: ProductList },
          { path: 'create', name: 'product-create', component: ProductCreate },
          { 
            path: ':id', 
            name: 'product-detail', 
            component: ProductDetail,
            children: [
              { path: '', name: 'product-info', component: ProductInfo },
              { path: 'edit', name: 'product-edit', component: ProductEdit },
              { path: 'inventory', name: 'product-inventory', component: ProductInventory },
              { path: 'reviews', name: 'product-reviews', component: ProductReviews }
            ]
          }
        ]
      },
      
      // 👥 用户管理
      {
        path: 'users',
        component: UserManagement,
        children: [
          { path: '', name: 'user-list', component: UserList },
          { 
            path: ':id', 
            name: 'user-detail', 
            component: UserDetail,
            children: [
              { path: '', name: 'user-profile', component: UserProfile },
              { path: 'orders', name: 'user-orders', component: UserOrders },
              { path: 'permissions', name: 'user-permissions', component: UserPermissions }
            ]
          }
        ]
      },
      
      // 📊 订单管理
      {
        path: 'orders',
        component: OrderManagement,
        children: [
          { path: '', name: 'order-list', component: OrderList },
          { path: ':id', name: 'order-detail', component: OrderDetail }
        ]
      }
    ]
  }
]

📱 移动端应用结构

javascript
const routes = [
  {
    path: '/',
    component: AppLayout,
    children: [
      // 🏠 首页
      { path: '', name: 'home', component: Home },
      
      // 🛍️ 商城
      {
        path: 'shop',
        component: ShopLayout,
        children: [
          { path: '', name: 'shop-home', component: ShopHome },
          { path: 'category/:id', name: 'category', component: Category },
          { path: 'product/:id', name: 'product', component: ProductDetail },
          { path: 'cart', name: 'cart', component: ShoppingCart },
          { path: 'checkout', name: 'checkout', component: Checkout }
        ]
      },
      
      // 👤 个人中心
      {
        path: 'profile',
        component: ProfileLayout,
        meta: { requiresAuth: true },
        children: [
          { path: '', name: 'profile-home', component: ProfileHome },
          { path: 'orders', name: 'my-orders', component: MyOrders },
          { path: 'favorites', name: 'favorites', component: Favorites },
          { path: 'settings', name: 'settings', component: Settings },
          { path: 'address', name: 'address', component: AddressManagement }
        ]
      }
    ]
  }
]

🎯 最佳实践

✅ 推荐做法

  1. 🎯 合理的层次结构

    javascript
    // ✅ 清晰的层次关系
    {
      path: '/user/:id',
      component: UserLayout,
      children: [
        { path: '', component: UserHome },
        { path: 'profile', component: UserProfile },
        { path: 'settings', component: UserSettings }
      ]
    }
  2. 🏷️ 使用命名路由

    javascript
    // ✅ 便于导航和维护
    { path: 'profile', name: 'user-profile', component: UserProfile }
  3. 🎨 共享布局组件

    vue
    <!-- ✅ 父组件提供公共布局 -->
    <template>
      <div class="layout">
        <header><!-- 公共头部 --></header>
        <nav><!-- 导航菜单 --></nav>
        <main>
          <router-view />
        </main>
        <footer><!-- 公共底部 --></footer>
      </div>
    </template>
  4. 🔒 路由级别的权限控制

    javascript
    // ✅ 在父路由设置权限
    {
      path: '/admin',
      component: AdminLayout,
      meta: { requiresAuth: true, role: 'admin' },
      children: [
        // 所有子路由都会继承权限要求
      ]
    }

⚠️ 注意事项

  1. 路径规则

    • 子路由路径不要以 / 开头(除非你想要根路径)
    • 空路径 '' 用于默认子路由
  2. 组件复用

    • 父组件在子路由切换时会被复用
    • 需要监听路由变化来更新数据
  3. 性能考虑

    • 避免过深的嵌套层次
    • 考虑使用路由懒加载

🚀 下一步学习

掌握了嵌套路由后,建议继续学习:

  1. 命名路由 - 更优雅的路由管理
  2. 路由守卫 - 实现权限控制
  3. 路由懒加载 - 优化应用性能
  4. 路由元信息 - 添加路由级别的数据

🎉 恭喜!你已经掌握了嵌套路由

现在你可以构建具有复杂层次结构的现代 Web 应用了!继续探索更多 Vue Router 的强大功能吧 🚀

🚀 Vue Router - 让前端路由变得简单而强大 | 构建现代化单页应用的最佳选择