嵌套路由 | 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 }
]
}
]
}
]🎯 最佳实践
✅ 推荐做法
🎯 合理的层次结构
javascript// ✅ 清晰的层次关系 { path: '/user/:id', component: UserLayout, children: [ { path: '', component: UserHome }, { path: 'profile', component: UserProfile }, { path: 'settings', component: UserSettings } ] }🏷️ 使用命名路由
javascript// ✅ 便于导航和维护 { path: 'profile', name: 'user-profile', component: UserProfile }🎨 共享布局组件
vue<!-- ✅ 父组件提供公共布局 --> <template> <div class="layout"> <header><!-- 公共头部 --></header> <nav><!-- 导航菜单 --></nav> <main> <router-view /> </main> <footer><!-- 公共底部 --></footer> </div> </template>🔒 路由级别的权限控制
javascript// ✅ 在父路由设置权限 { path: '/admin', component: AdminLayout, meta: { requiresAuth: true, role: 'admin' }, children: [ // 所有子路由都会继承权限要求 ] }
⚠️ 注意事项
路径规则
- 子路由路径不要以
/开头(除非你想要根路径) - 空路径
''用于默认子路由
- 子路由路径不要以
组件复用
- 父组件在子路由切换时会被复用
- 需要监听路由变化来更新数据
性能考虑
- 避免过深的嵌套层次
- 考虑使用路由懒加载
🚀 下一步学习
掌握了嵌套路由后,建议继续学习:
🎉 恭喜!你已经掌握了嵌套路由
现在你可以构建具有复杂层次结构的现代 Web 应用了!继续探索更多 Vue Router 的强大功能吧 🚀