各位靓仔靓女们,晚上好!我是你们的老朋友,人称“代码界的段子手”的…咳咳,今天咱们不讲段子,讲点硬核的,聊聊 Vue Router 里面的两个重要角色:RouterView
和 RouterLink
。
准备好了吗?要发车了!
第一部分:路由的基石:RouterView
RouterView
,顾名思义,就是用来“看”路由的组件。它负责根据当前路由,渲染对应的组件。你可以把它想象成一个占位符,一个容器,或者更形象一点,一个“舞台”,路由对应的组件就是在这个舞台上表演的演员。
1. 核心职责:渲染组件
RouterView
的核心职责就是渲染与当前路由匹配的组件。这个“匹配”的过程,是由 Vue Router 的路由匹配算法决定的。一旦匹配成功,RouterView
就会拿到对应的组件,然后把它渲染到页面上。
2. 源码剖析:简单粗暴的渲染
让我们简单看看 RouterView
的源码(简化版,只保留核心逻辑):
// src/components/view.js (简化版)
import { h, inject, computed } from 'vue'
import { RouterViewContext } from '../injectionSymbols'
export const RouterView = {
name: 'RouterView',
props: {
name: {
type: String,
default: 'default',
},
},
setup(props, { attrs }) {
const injectedRoute = inject(RouterViewContext, {}) // 从父级注入路由信息
const depth = injectedRoute.depth || 0
const matchedRouteRef = computed(() => {
const route = injectedRoute.route
return route.value.matched[depth]
})
return () => {
const route = injectedRoute.route.value;
const matchedRoute = matchedRouteRef.value;
if (!matchedRoute) {
return null // 没有匹配的路由,就什么也不渲染
}
const Component = matchedRoute.components[props.name]; // 获取组件
if (!Component) {
return null;
}
// 渲染组件
return h(Component, {
...attrs,
route: route
})
}
},
}
这段代码虽然简化了,但足以说明 RouterView
的工作原理:
-
inject(RouterViewContext, {})
: 通过inject
API,从父级组件(通常是Router
组件或者其他的RouterView
)注入路由信息。RouterViewContext
是一个Symbol
,作为依赖注入的 key。这是一种父子组件间传递数据的巧妙方式,避免了 props 逐层传递的麻烦。depth
表示当前 RouterView 的嵌套深度。 -
matchedRouteRef = computed(() => ...)
: 使用computed
来创建一个响应式的计算属性,根据当前路由的matched
数组和depth
,找到匹配的路由记录 (matchedRoute
)。matched
数组包含了所有匹配的路由记录,从最外层到最内层。 -
const Component = matchedRoute.components[props.name]
: 从matchedRoute
的components
属性中,根据props.name
获取要渲染的组件。props.name
允许我们使用命名视图,同一个路由可以渲染多个组件到不同的RouterView
中。如果没有指定name
,默认使用 ‘default’。 -
return h(Component, { ...attrs, route: route })
: 使用h
函数(Vue 3 中的createElement
),创建一个 VNode,表示要渲染的组件。同时,将attrs
(传递给RouterView
的所有 attribute) 和当前route
对象作为 props 传递给组件。
3. 多层嵌套:递归渲染的艺术
RouterView
支持多层嵌套,这使得我们可以构建复杂的页面结构。当一个路由匹配到多个组件时,每个 RouterView
都会负责渲染其中的一个组件。
例如,我们有以下路由配置:
const routes = [
{
path: '/users/:id',
component: () => import('./views/UserLayout.vue'),
children: [
{
path: 'profile',
component: () => import('./views/UserProfile.vue'),
},
{
path: 'posts',
component: () => import('./views/UserPosts.vue'),
},
],
},
]
在 UserLayout.vue
中,我们需要放置一个 RouterView
来渲染 UserProfile.vue
或者 UserPosts.vue
:
<template>
<div>
<h1>User Layout</h1>
<router-view />
</div>
</template>
当访问 /users/123/profile
时,UserLayout.vue
会被渲染,并且内部的 RouterView
会渲染 UserProfile.vue
。
4. 命名视图:灵活的布局
命名视图允许我们在同一个路由下,渲染多个组件到不同的 RouterView
中。这对于创建复杂的布局非常有用,比如页面头部、侧边栏、内容区域等等。
路由配置:
const routes = [
{
path: '/dashboard',
components: {
default: () => import('./views/Dashboard.vue'),
sidebar: () => import('./components/Sidebar.vue'),
header: () => import('./components/Header.vue'),
},
},
]
模板:
<template>
<div class="dashboard">
<header>
<router-view name="header" />
</header>
<aside>
<router-view name="sidebar" />
</aside>
<main>
<router-view />
</main>
</div>
</template>
在这个例子中,/dashboard
路由会同时渲染 Dashboard.vue
到默认的 RouterView
,Sidebar.vue
到 name
为 "sidebar" 的 RouterView
,Header.vue
到 name
为 "header" 的 RouterView
。
总结:RouterView
是路由渲染的核心,它通过递归和命名视图,实现了灵活的页面布局。
特性 | 描述 |
---|---|
核心职责 | 渲染与当前路由匹配的组件 |
嵌套渲染 | 支持多层嵌套,实现复杂的页面结构 |
命名视图 | 允许在同一个路由下渲染多个组件到不同的 RouterView 中,实现灵活的布局 |
依赖注入 | 通过 inject 从父级组件获取路由信息,避免了 props 逐层传递的麻烦 |
第二部分:路由的导航:RouterLink
RouterLink
,是 Vue Router 提供的用于导航的组件。它就像 HTML 中的 <a>
标签,但是它利用 Vue Router 的路由机制,实现了无刷新的页面跳转。
1. 核心职责:创建导航链接
RouterLink
的核心职责是创建一个可以触发路由变化的链接。当用户点击 RouterLink
时,它会调用 Vue Router 的 push
或者 replace
方法,更新浏览器的 URL,并触发 RouterView
的重新渲染。
2. 源码剖析:精心设计的导航
让我们看看 RouterLink
的源码(同样是简化版):
// src/components/link.js (简化版)
import { h, computed, getCurrentInstance, inject } from 'vue'
import { useRouter } from '../composables/router'
import { useRoute } from '../composables/route'
export const RouterLink = {
name: 'RouterLink',
props: {
to: {
type: [String, Object],
required: true,
},
tag: {
type: String,
default: 'a',
},
custom: {
type: Boolean,
},
replace: {
type: Boolean,
default: false,
},
},
setup(props, { slots }) {
const router = useRouter() // 获取 router 实例
const route = useRoute() // 获取 route 实例
const currentRoute = computed(() => {
return router.resolve(props.to) // 解析目标路由
})
const navigate = (e) => {
if (isLinkEvent(e)) {
if (props.replace) {
router.replace(props.to)
} else {
router.push(props.to)
}
}
}
return () => {
const { resolved, href } = currentRoute.value;
const activeClass = 'router-link-active'
const exactActiveClass = 'router-link-exact-active'
const currentClasses = [];
if (resolved.matched.length && resolved.resolved.path === route.path) {
currentClasses.push(exactActiveClass);
}
const link = h(
props.tag,
{
href: href,
onClick: navigate,
class: currentClasses
},
slots.default && slots.default()
)
return props.custom
? slots.default({
href,
navigate,
route: resolved
})
: link
}
},
}
function isLinkEvent(event) {
return (
event.button === 0 &&
isModifierAllowed(event) &&
(!event.defaultPrevented)
)
}
function isModifierAllowed (event) {
const { metaKey, altKey, ctrlKey, shiftKey } = event
return !(metaKey || altKey || ctrlKey || shiftKey)
}
这段代码展示了 RouterLink
的核心逻辑:
-
useRouter()
和useRoute()
: 使用useRouter
和useRoute
composables 获取router
实例和route
实例。router
实例提供了push
、replace
等方法,用于导航;route
实例包含了当前路由的信息。 -
currentRoute = computed(() => router.resolve(props.to))
: 使用router.resolve(props.to)
解析props.to
,得到一个包含resolved
(解析后的路由对象) 和href
(实际的 URL) 的对象。router.resolve
非常重要,它负责将to
属性(可以是字符串或对象)转换为一个完整的路由对象。 -
navigate = (e) => ...
: 定义一个navigate
函数,用于处理点击事件。当用户点击RouterLink
时,会调用这个函数。它会判断是否是有效的链接点击事件(例如,不是右键点击或者使用了 modifier keys),然后根据props.replace
的值,调用router.push
或者router.replace
方法,进行导航。 -
h(props.tag, { ... }, slots.default && slots.default())
: 使用h
函数创建一个 VNode,表示一个 HTML 元素。props.tag
允许我们指定要渲染的 HTML 标签,默认为<a>
。href
属性设置为解析后的 URL,onClick
事件设置为navigate
函数。slots.default()
渲染RouterLink
的子节点,也就是链接的文本。 -
activeClass
和exactActiveClass
: 动态添加 CSS 类名,标识当前激活的链接。activeClass
在当前路由是目标路由的子路由时生效,exactActiveClass
在当前路由与目标路由完全匹配时生效。 -
props.custom
: 支持自定义渲染。如果props.custom
为true
,则RouterLink
不会直接渲染一个<a>
标签,而是将href
和navigate
函数作为 props 传递给插槽,由用户自定义渲染。
3. to
属性:灵活的路由目标
to
属性是 RouterLink
最重要的属性,它指定了导航的目标路由。to
属性可以是字符串或者对象。
-
字符串: 表示一个绝对路径或者相对路径。例如:
<router-link to="/users/123">User Profile</router-link> <router-link to="profile">My Profile</router-link>
-
对象: 允许我们更灵活地指定路由参数。例如:
<router-link :to="{ path: '/users', query: { page: 2 } }">Users</router-link> <router-link :to="{ name: 'user', params: { id: 123 } }">User Profile</router-link>
4. replace
属性:替换历史记录
replace
属性决定了导航的方式。如果 replace
为 true
,则会调用 router.replace
方法,替换浏览器的历史记录。否则,会调用 router.push
方法,添加一条新的历史记录。
5. active-class
和 exact-active-class
:激活状态的样式
active-class
和 exact-active-class
属性用于指定当 RouterLink
处于激活状态时,要添加的 CSS 类名。active-class
会在当前路由是目标路由的子路由时生效,exact-active-class
会在当前路由与目标路由完全匹配时生效。
6. custom
属性:自定义渲染
custom
属性允许我们完全自定义 RouterLink
的渲染方式。当 custom
为 true
时,RouterLink
不会直接渲染一个 <a>
标签,而是将 href
和 navigate
函数作为 props 传递给插槽,由用户自定义渲染。
例如:
<router-link to="/about" v-slot="{ href, navigate, route }" custom>
<button :href="href" @click.prevent="navigate">
Go to {{ route.path }}
</button>
</router-link>
在这个例子中,我们使用 custom
属性,将 href
和 navigate
函数传递给插槽,然后在插槽中使用一个 <button>
元素来渲染链接。
总结:RouterLink
是路由导航的核心,它通过 to
属性指定导航目标,通过 replace
属性控制导航方式,通过 active-class
和 exact-active-class
属性控制激活状态的样式,通过 custom
属性支持自定义渲染。
特性 | 描述 |
---|---|
核心职责 | 创建可以触发路由变化的导航链接 |
to 属性 |
指定导航的目标路由,可以是字符串或者对象 |
replace 属性 |
决定导航的方式,true 表示替换历史记录,false 表示添加新的历史记录 |
active-class 和 exact-active-class |
用于指定当 RouterLink 处于激活状态时,要添加的 CSS 类名 |
custom 属性 |
允许完全自定义 RouterLink 的渲染方式,将 href 和 navigate 函数传递给插槽 |
第三部分:RouterView
和 RouterLink
如何与路由实例交互
RouterView
和 RouterLink
都需要与 Vue Router 的路由实例进行交互,才能实现路由的渲染和导航。
-
RouterView
: 通过inject
API,从父级组件(通常是Router
组件或者其他的RouterView
)注入路由信息。这些信息包括当前路由对象 (route
)、路由匹配结果 (matched
) 等。RouterView
根据这些信息,决定要渲染哪个组件。 -
RouterLink
: 通过useRouter
composable 获取router
实例。RouterLink
使用router.resolve
方法解析to
属性,得到一个包含resolved
和href
的对象。当用户点击RouterLink
时,它会调用router.push
或者router.replace
方法,更新浏览器的 URL,并触发RouterView
的重新渲染。
可以用一个表格简单总结:
组件 | 交互方式 | 作用 |
---|---|---|
RouterView |
inject 从父级组件获取路由信息(当前路由对象、路由匹配结果等) |
根据路由信息,决定要渲染哪个组件 |
RouterLink |
useRouter 获取 router 实例;使用 router.resolve 解析 to 属性;调用 router.push 或者 router.replace 方法 |
创建导航链接,当用户点击链接时,更新浏览器的 URL,并触发 RouterView 的重新渲染 |
第四部分:总结与升华
今天我们深入剖析了 Vue Router 中的 RouterView
和 RouterLink
组件。我们了解了它们的核心职责、源码实现,以及它们如何与路由实例交互。
RouterView
负责根据当前路由渲染对应的组件,是路由渲染的核心。RouterLink
负责创建导航链接,是路由导航的核心。
这两个组件相互配合,共同完成了 Vue Router 的路由功能。
希望今天的分享对你有所帮助。记住,理解框架的底层原理,才能更好地使用框架,写出更健壮、更高效的代码。
下次有机会,我们再聊聊 Vue Router 的其他部分,比如路由匹配算法、导航守卫等等。
感谢大家的聆听!下课!