观众朋友们,晚上好!我是老码,今晚咱们聊聊 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
是父路由,UserProfile
和UserPosts
是它的子路由。- 访问
/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>© 2023 My Blog</p>
</footer>
</div>
</template>
解释:
- 在路由配置中,
components
对象定义了三个组件:BlogContent
(默认),BlogSidebar
和BlogComments
。 - 在
App.vue
中,使用了三个<router-view>
,其中一个没有name
属性,它就是默认的<router-view>
,对应BlogContent
。另外两个<router-view>
分别命名为sidebar
和comments
,对应BlogSidebar
和BlogComments
。 - 当访问
/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
,它定义了页面的整体布局。profile
和orders
是子路由,它们都使用了命名视图。UserSidebar
组件作为导航栏,在profile
和orders
页面都会显示。- 访问
/user/123/profile
时,会显示导航栏和个人信息。 - 访问
/user/123/orders
时,会显示导航栏和订单列表。
四、注意事项:
-
路由的顺序: 路由的顺序很重要,Vue Router 会按照定义的顺序匹配路由。如果两个路由的
path
有重叠,那么先定义的路由会优先匹配。例如,如果先定义了/user/:id
,再定义了/user/profile
,那么访问/user/profile
时,会被匹配到/user/:id
,profile
会被当做id
的值。所以,应该将更具体的路由放在前面。 -
exact
属性:<router-link>
默认会进行模糊匹配,只要当前路由包含了to
属性的值,就会被认为是激活状态。如果你需要精确匹配,可以使用exact
属性。例如:<router-link to="/user/profile" exact>Profile</router-link>
。 -
active-class
属性:<router-link>
默认激活时的 class 是router-link-active
。 你可以使用active-class
属性自定义激活时的 class。 例如:<router-link to="/user/profile" active-class="active-menu">Profile</router-link>
。 -
router-link
的to
属性可以使用对象: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>
-
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 应用的路由,创建更复杂、更灵活、更易于维护的用户界面。希望通过今天的讲解,大家能够对它们有更深入的理解,并在实际项目中灵活运用。 记住,编码的乐趣在于不断学习和实践,多尝试,多思考,你也能成为路由高手!下课!