各位靓仔靓女,今天咱们来聊聊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 的实现原理。如果有什么问题,欢迎随时提问。下次再见!