Vue嵌套路由

在單頁應用裏,“頁面”不再是整屏刷新,而是由路由驅動的組件樹。當業務複雜到「用户中心 → 個人資料 / 收貨地址 / 賬號安全 / 好友列表」這種層級時,嵌套路由(Nested Routes)是唯一能把深度與可維護性同時保留下來的方案。

一、嵌套路由到底在解決什麼問題

想象一個用户中心:

/user                 用户中心外殼(Layout)
├── /user/profile     個人資料
├── /user/address     收貨地址
├── /user/security    賬號安全
└── /user/friends     好友列表

如果寫成平級路由,每切換一個子頁面就要重新加載整個外殼(導航、側邊欄、用户信息),浪費、卡頓、狀態丟失。

嵌套路由讓外殼只掛載一次,子頁面作為 <router-view> 的局部插槽渲染,完美複用外殼,並天然支持麪包屑、標籤頁、權限控制。

二、一條代碼看全貌

// router/index.js
const routes = [
  {
    path: '/user',
    component: () => import('@/views/user/Layout.vue'), // 外殼
    children: [
      { path: '',           component: () => import('@/views/user/Profile.vue') },
      { path: 'address',    component: () => import('@/views/user/Address.vue') },
      { path: 'security',   component: () => import('@/views/user/Security.vue') },
      { path: 'friends',    component: () => import('@/views/user/Friends.vue') }
    ]
  }
]

要點:

  • 層級關係 = 文件系統:父路由的 component 是文件夾,children 是裏面的文件。
  • 默認子路由 = 空字符串 '',訪問 /user 時自動渲染 Profile
  • 路徑寫法 = 相對路徑:address 會自動拼接成 /user/address,無需手寫全量。

三、Layout 組件

<!-- views/user/Layout.vue -->
<template>
  <div class="user-center">
    <aside>
      <router-link to="/user">個人資料</router-link>
      <router-link to="/user/address">收貨地址</router-link>
      <router-link to="/user/security">賬號安全</router-link>
      <router-link to="/user/friends">好友列表</router-link>
    </aside>
    <main>
      <router-view />   <!-- 子路由插在這裏 -->
    </main>
  </div>
</template>

子頁面渲染時,Layout 組件不會重新創建,導航高亮、用户信息、WebSocket 連接全部保持。

四、動態路由 + 嵌套:URL 即狀態

把用户 ID 塞進路徑:

{
  path: '/user/:id',
  component: () => import('@/views/user/Layout.vue'),
  props: true,              // 把 id 作為 prop 注入 Layout
  children: [
    { path: '',        component: () => import('@/views/user/Profile.vue'), props: true },
    { path: 'address', component: () => import('@/views/user/Address.vue'),  props: true }
  ]
}

訪問 /user/42/address 時:

  • Layout 通過 props.id 拿到 42,去拉用户信息;
  • Address 通過 props.id 再去拉地址列表;
  • 切換子路由只改後半段,外殼複用,接口只增不重複。

五、項目實踐

1.權限與麪包屑

{
  path: '/user',
  component: Layout,
  meta: { title: '用户中心', needAuth: true },
  children: [
    { path: '', meta: { title: '個人資料' } },
    { path: 'address', meta: { title: '收貨地址' } }
  ]
}

全局後置鈎子:

router.afterEach(to => {
  document.title = to.matched
    .map(r => r.meta.title)
    .filter(Boolean)
    .join(' - ')
})

matched 數組從根到當前節點依次展開,天然就是麪包屑數據源。

權限同理:在導航守衞裏檢查 to.matched.some(r => r.meta.needAuth),一次遞歸即可拿到所有層級要求。

2.代碼分割

  • 父路由同步加載:外殼體積小,保證首屏骨架秒出;
  • 子路由全部懶加載:利用魔法註釋給 chunk 命名,方便 CDN 緩存。
component: () =>
  import(/* webpackChunkName: "user-security" */ '@/views/user/Security.vue')

六、常見問題

  • 空路徑與斜槓:path: ''path: '/' 都匹配 /user,但後者會額外觸發重定向,導致外殼重複渲染。
  • 深度監聽失效:在 Layoutwatch $route 時,記得加 immediate: true,否則首次進入不觸發。
  • 滾動位置丟失:給 <router-view>key="$route.fullPath" 可強制重新掛載,但會破壞緩存;更優解是在 activated 鈎子裏手動恢復 scrollTop。