阐述 Vue Router 中的嵌套路由(Nested Routes)和命名视图(Named Views)的应用场景。

观众朋友们,晚上好!我是老码,今晚咱们聊聊 Vue Router 里的两把刷子:嵌套路由和命名视图。这两兄弟能让你的 Vue 应用界面组织得井井有条,逻辑清晰,代码也更优雅。别害怕,这俩家伙其实不难搞,跟着老码,保证你学完能Hold住。

一、嵌套路由:层层叠叠的惊喜

想象一下,你正在做一个电商网站。首页、商品列表页、商品详情页、订单页、个人中心… 这么多页面,如果全都平铺在第一层路由,那路由表得有多长?维护起来简直是噩梦。

这时候,嵌套路由就派上用场了。它就像一个文件夹系统,允许你将相关的路由组织在一起,形成层级结构。

1. 适用场景:

  • 复杂的页面结构: 比如电商网站,个人中心下又分成了订单管理、地址管理、账户安全等子页面。
  • 具有包含关系的页面: 比如论坛,帖子列表页包含多个帖子详情页。
  • 需要共享布局的页面: 比如后台管理系统,每个子页面都共享侧边栏和头部。

2. 核心概念:

  • 父路由: 包含其他路由的路由。
  • 子路由: 嵌套在父路由中的路由。
  • <router-view> 的嵌套: 父组件和子组件都需要 <router-view> 来显示对应的内容。

3. 代码示例:

// routes.js
const routes = [
  {
    path: '/user',
    component: User, // 父组件
    children: [
      {
        path: 'profile', // 注意这里没有 /
        component: UserProfile
      },
      {
        path: 'posts',
        component: UserPosts
      }
    ]
  }
]
// User.vue (父组件)
<template>
  <div>
    <h1>User Page</h1>
    <router-link to="/user/profile">Profile</router-link> |
    <router-link to="/user/posts">Posts</router-link>
    <router-view></router-view> <!-- 子组件显示在这里 -->
  </div>
</template>
// UserProfile.vue (子组件)
<template>
  <div>
    <h2>User Profile</h2>
    <p>Welcome to your profile!</p>
  </div>
</template>
// UserPosts.vue (子组件)
<template>
  <div>
    <h2>User Posts</h2>
    <p>Here are your latest posts.</p>
  </div>
</template>

解释:

  • /user 是父路由,UserProfileUserPosts 是它的子路由。
  • 访问 /user/profile 时,Vue 会先渲染 User.vue,然后在 User.vue<router-view> 中渲染 UserProfile.vue
  • 注意子路由的 path 中没有 /,因为它是相对于父路由的。

4. 嵌套路由的参数传递:

父子路由之间也需要传递参数,这也很简单。

// routes.js
const routes = [
  {
    path: '/user/:userId', //父路由带参数
    component: User,
    children: [
      {
        path: 'profile',
        component: UserProfile
      },
      {
        path: 'posts',
        component: UserPosts
      }
    ]
  }
]
// User.vue (父组件)
<template>
  <div>
    <h1>User Page - User ID: {{ userId }}</h1>
    <router-link :to="`/user/${userId}/profile`">Profile</router-link> |
    <router-link :to="`/user/${userId}/posts`">Posts</router-link>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  computed: {
    userId() {
      return this.$route.params.userId; // 通过 $route.params 获取父路由参数
    }
  }
}
</script>
// UserProfile.vue (子组件)
<template>
  <div>
    <h2>User Profile - User ID: {{ userId }}</h2>
    <p>Welcome to your profile!</p>
  </div>
</template>

<script>
export default {
  computed: {
    userId() {
      return this.$route.params.userId; // 子组件也能直接获取父路由参数
    }
  }
}
</script>

解释:

  • 父路由 /user/:userId 定义了一个参数 userId
  • 在父组件 User.vue 中,可以通过 this.$route.params.userId 获取到这个参数。
  • 子组件 UserProfile.vue 也能直接通过 this.$route.params.userId 获取到父路由的参数。 这就是嵌套路由的方便之处。

二、命名视图:一屏多显的艺术

有时候,你需要在同一个页面上显示多个独立的组件,而且这些组件的内容是基于不同的路由。例如,一个博客页面,可能同时显示文章内容、评论列表和相关文章。

如果使用传统的 <router-view>,你只能在一个地方显示一个组件。这时候,命名视图就闪亮登场了!

1. 适用场景:

  • 需要在同一个页面显示多个组件,且这些组件对应不同的路由。
  • 需要灵活地控制页面布局,将不同的组件放置在不同的位置。
  • 需要在多个组件之间共享路由参数。

2. 核心概念:

  • 命名 <router-view><router-view> 加上 name 属性,每个 <router-view> 对应一个名字。
  • components 选项: 在路由配置中使用 components 选项,而不是 component 选项,components 是一个对象,键是 <router-view> 的名字,值是对应的组件。

3. 代码示例:

// routes.js
const routes = [
  {
    path: '/blog',
    components: {
      default: BlogContent, // 默认的 <router-view>
      sidebar: BlogSidebar,
      comments: BlogComments
    }
  }
]
// App.vue (根组件)
<template>
  <div id="app">
    <header>
      <h1>My Blog</h1>
    </header>
    <main>
      <div class="content">
        <router-view></router-view>  <!-- 默认的 <router-view>,对应 BlogContent -->
      </div>
      <div class="sidebar">
        <router-view name="sidebar"></router-view>  <!-- 命名的 <router-view>,对应 BlogSidebar -->
      </div>
      <div class="comments">
        <router-view name="comments"></router-view>  <!-- 命名的 <router-view>,对应 BlogComments -->
      </div>
    </main>
    <footer>
      <p>&copy; 2023 My Blog</p>
    </footer>
  </div>
</template>

解释:

  • 在路由配置中,components 对象定义了三个组件:BlogContent (默认),BlogSidebarBlogComments
  • App.vue 中,使用了三个 <router-view>,其中一个没有 name 属性,它就是默认的 <router-view>,对应 BlogContent。另外两个 <router-view> 分别命名为 sidebarcomments,对应 BlogSidebarBlogComments
  • 当访问 /blog 时,三个组件会同时渲染到页面上,并且分别显示在不同的位置。

4. 命名视图的参数传递:

命名视图也可以传递参数,方法和普通路由一样,通过 this.$route.params 获取。

// routes.js
const routes = [
  {
    path: '/blog/:blogId',
    components: {
      default: BlogContent,
      sidebar: BlogSidebar,
      comments: BlogComments
    }
  }
]
// BlogContent.vue (组件)
<template>
  <div>
    <h2>Blog Content - Blog ID: {{ blogId }}</h2>
    <p>This is the content of blog post {{ blogId }}.</p>
  </div>
</template>

<script>
export default {
  computed: {
    blogId() {
      return this.$route.params.blogId; // 获取路由参数
    }
  }
}
</script>

解释:

  • 路由 /blog/:blogId 定义了一个参数 blogId
  • BlogContent.vue 中,可以通过 this.$route.params.blogId 获取到这个参数。其他命名视图对应的组件也可以用同样的方式获取。

三、嵌套路由和命名视图的结合:更强大的组合

嵌套路由和命名视图可以结合使用,创建更复杂的页面结构。想象一下,在个人中心页面,你希望左侧显示导航栏,右侧显示个人信息或订单列表。

// routes.js
const routes = [
  {
    path: '/user/:userId',
    component: UserLayout, // 包含导航栏和内容区域的布局组件
    children: [
      {
        path: 'profile',
        components: {
          default: UserProfile, // 个人信息
          sidebar: UserSidebar // 导航栏
        }
      },
      {
        path: 'orders',
        components: {
          default: UserOrders, // 订单列表
          sidebar: UserSidebar // 导航栏
        }
      }
    ]
  }
]
// UserLayout.vue (布局组件)
<template>
  <div class="user-layout">
    <div class="sidebar">
      <router-view name="sidebar"></router-view> <!-- 导航栏 -->
    </div>
    <div class="content">
      <router-view></router-view> <!-- 内容区域 -->
    </div>
  </div>
</template>

解释:

  • /user/:userId 是父路由,对应 UserLayout.vue,它定义了页面的整体布局。
  • profileorders 是子路由,它们都使用了命名视图。
  • UserSidebar 组件作为导航栏,在 profileorders 页面都会显示。
  • 访问 /user/123/profile 时,会显示导航栏和个人信息。
  • 访问 /user/123/orders 时,会显示导航栏和订单列表。

四、注意事项:

  1. 路由的顺序: 路由的顺序很重要,Vue Router 会按照定义的顺序匹配路由。如果两个路由的 path 有重叠,那么先定义的路由会优先匹配。例如,如果先定义了 /user/:id,再定义了 /user/profile,那么访问 /user/profile 时,会被匹配到 /user/:idprofile 会被当做 id 的值。所以,应该将更具体的路由放在前面。

  2. exact 属性: <router-link> 默认会进行模糊匹配,只要当前路由包含了 to 属性的值,就会被认为是激活状态。如果你需要精确匹配,可以使用 exact 属性。例如:<router-link to="/user/profile" exact>Profile</router-link>

  3. active-class 属性: <router-link> 默认激活时的 class 是 router-link-active。 你可以使用 active-class 属性自定义激活时的 class。 例如:<router-link to="/user/profile" active-class="active-menu">Profile</router-link>

  4. router-linkto 属性可以使用对象: to 属性除了可以使用字符串,还可以使用对象,这样可以更灵活地定义路由。例如:

    <router-link :to="{ path: '/user/profile', query: { page: 1 } }">Profile</router-link>

    或者:

    <router-link :to="{ name: 'user-profile', params: { userId: 123 } }">Profile</router-link>
  5. beforeRouteEnter, beforeRouteUpdate, beforeRouteLeave 路由守卫

    这些路由守卫用于在路由切换时执行一些逻辑,例如权限验证、数据加载、页面跳转等。

    • beforeRouteEnter: 在进入路由之前执行,不能访问 this
    • beforeRouteUpdate: 在当前路由改变,但是该组件被复用时调用。可以访问 this
    • beforeRouteLeave: 在离开路由之前执行,可以访问 this

    例子:

    export default {
      beforeRouteEnter(to, from, next) {
        // 在渲染该组件的对应路由被 confirm 前调用
        // 不!能!获取组件实例 `this` !
        // 因为当守卫执行前,组件实例还没被创建
        next()
      },
      beforeRouteUpdate(to, from, next) {
        // 在当前路由改变,但是该组件被复用时调用
        // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
        // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
        // 可以访问组件实例 `this`
        next()
      },
      beforeRouteLeave(to, from, next) {
        // 导航离开该组件的对应路由时调用
        // 可以访问组件实例 `this`
        next()
      }
    }

五、表格总结:

特性 嵌套路由 命名视图 结合使用
适用场景 复杂页面结构、包含关系页面、共享布局页面 同一页面显示多个组件、灵活控制页面布局、共享路由参数 更复杂的页面结构,例如个人中心页面,左侧导航栏,右侧显示个人信息或订单列表
核心概念 父路由、子路由、<router-view> 的嵌套 命名 <router-view>components 选项 嵌套路由定义整体结构,命名视图定义每个结构内的组件
优势 方便组织和管理路由、代码结构清晰、易于维护 可以在同一页面显示多个组件、灵活性高、可以创建更丰富的用户界面 组合两者的优势,可以创建非常复杂的页面结构、代码可维护性高、用户体验好
注意事项 路由顺序、参数传递方式 命名 <router-view> 的名字要和 components 选项中的键对应 考虑整体结构和每个部分的组件
示例 电商网站的商品列表页和商品详情页、后台管理系统的页面 博客页面同时显示文章内容、评论列表和相关文章 个人中心页面,左侧导航栏,右侧显示个人信息或订单列表

六、结语:

嵌套路由和命名视图是 Vue Router 中非常实用的两个特性,掌握它们可以让你更好地组织和管理你的 Vue 应用的路由,创建更复杂、更灵活、更易于维护的用户界面。希望通过今天的讲解,大家能够对它们有更深入的理解,并在实际项目中灵活运用。 记住,编码的乐趣在于不断学习和实践,多尝试,多思考,你也能成为路由高手!下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注