各位靓仔靓女,今天咱们来聊聊Vue Router里的两个重要角色:RouterView
和 RouterLink
。别害怕,虽然听起来高大上,但拆开了看,其实就是两个平易近人的组件。咱们要做的,就是扒开它们的外衣,看看它们是怎么跟 Vue Router 这个“老大哥”眉来眼去的。
开场白:RouterView 和 RouterLink 的职责
简单来说:
RouterView
:一个“占位符”,告诉 Vue:“嘿,兄弟,这里要显示匹配当前路由的组件了!”RouterLink
:一个“导航员”,负责生成链接,点击它就能触发路由切换。
说白了,一个负责显示内容,一个负责切换内容。
第一幕:RouterView 的“占位艺术”
RouterView
的核心任务是根据当前路由,动态渲染对应的组件。它是怎么做到的呢?咱们先来简化一下 RouterView
的源码,看看它的骨架:
// 简化版 RouterView
const RouterView = {
name: 'RouterView',
functional: true, // 函数式组件,性能更棒
props: {
name: {
type: String,
default: 'default' // 命名视图
}
},
render(h, { parent, data, props }) {
let route = parent.$route; // 获取当前路由对象
let matched = route.matched; // 获取匹配的路由记录数组
data.routerView = true; // 标记这是一个 RouterView 组件
let depth = 0;
let parentRouteView = parent;
while (parentRouteView && parentRouteView.$vnode && parentRouteView.$vnode.data) {
if (parentRouteView.$vnode.data.routerView) {
depth++; // 计算 RouterView 的嵌套深度
}
parentRouteView = parentRouteView.$parent;
}
let record = matched[depth]; // 获取当前 RouterView 应该渲染的路由记录
if (!record) {
return h(); // 没有匹配的路由,渲染一个空 VNode
}
let component = record.components[props.name]; // 获取组件
if (!component) {
return h(); // 组件不存在,渲染一个空 VNode
}
const renderProps = { ...route.params, ...route.query }; // 合并参数
// 返回 VNode
return h(component, data, renderProps);
}
};
别被代码吓到,咱们一行行来解释:
functional: true
:RouterView
是一个函数式组件,这意味着它没有自己的状态,渲染性能更好。props: { name: ... }
:name
属性用于支持命名视图,允许多个RouterView
同时显示不同的组件。render(h, { parent, data, props })
:render
函数是函数式组件的核心,它负责生成 VNode。parent.$route
:获取当前路由对象。Vue Router 会把路由对象注入到每个组件的vm.$route
属性中。route.matched
:获取匹配的路由记录数组。路由记录包含了路由的配置信息,例如组件、路径等。depth
:计算RouterView
的嵌套深度。这个深度决定了哪个路由记录应该被渲染。比如,如果RouterView
嵌套了两层,那么它应该渲染route.matched[2]
对应的组件。record = matched[depth]
:根据深度获取对应的路由记录。component = record.components[props.name]
:从路由记录中获取组件。这里使用了props.name
来支持命名视图。h(component, data, renderProps)
:使用h
函数创建 VNode。component
是要渲染的组件,data
包含了 VNode 的属性,renderProps
包含了要传递给组件的 props。
关键点:路由匹配和嵌套深度
RouterView
的核心逻辑在于如何确定哪个路由记录应该被渲染。这涉及到两个关键概念:
- 路由匹配:Vue Router 会根据当前 URL,找到匹配的路由记录。匹配的路由记录会被存储在
route.matched
数组中。 - 嵌套深度:
RouterView
可以嵌套在其他组件中,因此需要计算它的嵌套深度,以确定它应该渲染route.matched
数组中的哪个元素。
举个例子:
// 路由配置
const routes = [
{
path: '/users',
component: Users,
children: [
{
path: ':id',
component: UserDetail
}
]
}
];
如果当前 URL 是 /users/123
,那么 route.matched
数组将会包含两个路由记录:
索引 | 路由记录 |
---|---|
0 | { path: '/users', component: Users } |
1 | { path: ':id', component: UserDetail } |
如果 RouterView
位于 Users
组件中,那么它的嵌套深度为 1,因此它应该渲染 route.matched[1]
对应的组件,也就是 UserDetail
。
第二幕:RouterLink 的“导航魔法”
RouterLink
的职责是生成链接,点击链接会触发路由切换。它的实现也比较简单:
// 简化版 RouterLink
const RouterLink = {
name: 'RouterLink',
props: {
to: {
type: [String, Object],
required: true
},
tag: {
type: String,
default: 'a' // 渲染成什么标签
},
activeClass: {
type: String,
default: 'router-link-active'
},
exactActiveClass: {
type: String,
default: 'router-link-exact-active'
}
},
computed: {
href() {
// 解析 to 属性,生成 href
return this.$router.resolve(this.to).href;
},
isActive() {
// 判断是否激活
const currentRoute = this.$route;
return this.pathToRegexp(this.href).test(currentRoute.path);
}
},
methods: {
pathToRegexp(path) {
const escapedPath = path.replace(/[-/\^$*+?.()|[]{}]/g, '\$&');
const regexpPattern = `^${escapedPath}$`;
return new RegExp(regexpPattern);
}
},
render(h) {
const tag = this.tag;
const data = {
attrs: {
href: this.href
},
class: {
[this.activeClass]: this.isActive
},
on: {
click: (e) => {
e.preventDefault(); // 阻止默认行为
this.$router.push(this.to); // 触发路由切换
}
}
};
return h(tag, data, this.$slots.default);
}
};
咱们也来一行行分析:
props: { to, tag, activeClass, exactActiveClass }
:to
:指定要导航到的路由。可以是字符串或对象。tag
:指定渲染成什么标签,默认为a
。activeClass
:指定激活时的 CSS 类名。exactActiveClass
:指定精确匹配时的 CSS 类名。
computed: { href, isActive }
:href
:根据to
属性,使用$router.resolve()
方法解析出实际的 URL。isActive
:判断当前路由是否激活。
render(h)
:生成 VNode。data.attrs.href
:设置链接的href
属性。data.class[this.activeClass]
:根据isActive
属性,动态添加activeClass
。data.on.click
:添加点击事件监听器,阻止默认行为,并使用$router.push()
方法触发路由切换。
关键点:$router.push()
和 $router.resolve()
RouterLink
的核心在于两个方法:
$router.push(to)
:触发路由切换。to
可以是字符串或对象。$router.resolve(to)
:解析to
属性,生成包含href
、route
、resolved
等信息的对象。
$router.resolve()
方法非常重要,它可以将 to
属性转换为实际的 URL,并进行路由匹配。
第三幕:RouterView 和 RouterLink 的“基情互动”
现在,咱们来看看 RouterView
和 RouterLink
是如何协同工作的。
RouterLink
生成链接:RouterLink
根据to
属性,生成链接,并设置href
属性。- 点击链接触发路由切换:当用户点击链接时,
RouterLink
阻止默认行为,并调用$router.push()
方法触发路由切换。 - Vue Router 更新路由对象:Vue Router 接收到路由切换的请求后,会更新当前的路由对象 (
$route
)。 RouterView
监听路由变化:RouterView
监听$route
的变化,当路由变化时,它会重新渲染,显示匹配当前路由的组件。
这个过程可以用一个简单的流程图来表示:
sequenceDiagram
participant RouterLink
participant Router
participant RouterView
participant Component
RouterLink->>Router: $router.push(to)
Router->>Router: 更新 $route
Router->>RouterView: 触发 RouterView 重新渲染
RouterView->>Component: 渲染匹配的组件
第四幕:深入源码,揭秘细节
咱们再深入源码,看看一些细节:
RouterView
的keep-alive
支持:RouterView
支持keep-alive
组件,可以缓存已经渲染过的组件,提高性能。RouterLink
的exact
属性:RouterLink
的exact
属性可以控制是否进行精确匹配。如果exact
属性为true
,那么只有当 URL 完全匹配to
属性时,才会激活。RouterLink
的append
属性:RouterLink
的append
属性可以控制是否在当前 URL 的基础上追加to
属性。
RouterView的keep-alive支持
<template>
<router-view>
<keep-alive>
<component :is="$route.matched[0].components.default" />
</keep-alive>
</router-view>
</template>
keep-alive
是 Vue 内置的组件,用于缓存组件。当 RouterView
渲染组件时,如果组件被 keep-alive
包裹,那么组件的状态会被缓存,下次再次渲染时,会直接从缓存中读取,避免重复创建组件。
RouterLink的activeClass 和 exactActiveClass
RouterLink 根据当前路由是否激活,动态添加 activeClass
和 exactActiveClass
。
activeClass
:只要当前路由包含to
属性,就会添加activeClass
。exactActiveClass
:只有当当前路由完全匹配to
属性时,才会添加exactActiveClass
。
比如,有如下路由配置:
const routes = [
{
path: '/users',
component: Users
},
{
path: '/users/:id',
component: UserDetail
}
];
如果当前路由是 /users/123
,那么:
<router-link to="/users">Users</router-link>
会添加activeClass
。<router-link to="/users" exact>Users</router-link>
不会添加exactActiveClass
。<router-link to="/users/123">UserDetail</router-link>
会添加activeClass
。<router-link to="/users/123" exact>UserDetail</router-link>
会添加exactActiveClass
。
RouterLink的append属性
append
属性用于控制是否在当前 URL 的基础上追加 to
属性。
比如,当前 URL 是 /users
,to
属性是 123
,那么:
<router-link to="123">User</router-link>
会生成/123
。<router-link to="123" append>User</router-link>
会生成/users/123
。
总结:RouterView 和 RouterLink 的“爱情故事”
RouterView
和 RouterLink
是 Vue Router 中两个至关重要的组件,它们一个负责显示内容,一个负责切换内容。它们通过 $route
对象和 $router
对象进行交互,共同完成了路由的功能。理解它们的实现原理,可以帮助我们更好地使用 Vue Router,并解决实际开发中遇到的问题。
表格总结:
组件 | 职责 | 核心属性/方法 | 关键点 |
---|---|---|---|
RouterView |
根据当前路由,动态渲染对应的组件 | route.matched , depth |
路由匹配, 嵌套深度 |
RouterLink |
生成链接,点击链接触发路由切换 | to , $router.push() , $router.resolve() |
路由切换, URL 解析 |
希望今天的分享能帮助大家更深入地理解 Vue Router 的实现原理。如果有什么问题,欢迎随时提问。下次再见!