这是我参与8月更文挑战的第25天,活动详情查看:8月更文挑战
一,前言
上篇,介绍了$route、$router 与 router-link 组件的实现,主要涉及以下内容:
- 定义原型方法
$route和$router; <router-link>组件的功能和实现;
本篇,介绍<router-view>组件的实现;
二,前文回顾
- 在 VueRouter 实例化时,通过路由匹配器 createMatcher 对路由配置进行扁平化处理;
- 路由匹配器 createMatcher 中的 match 方法,匹配路由记录并创建路由对象;
- 将当前匹配路由对象current,创建为响应式数据;
- 点击 router-link 组件进行路径切换,调用 transitionTo 匹配新路由记录并更新响应式数据 current,触发视图更新;
点击 router-link 组件进行路径切换时,需要根据最新路由地址匹配到全部路由规则,并递归进行页面的更新渲染操作;
三,函数式组件
router-view 是一个函数式组件,函数式组件有以下特点:
- 性能高,渲染时没有 this;
- functional: true 表示函数式组件;
export default {
name: 'routerView',
functional: true,// 函数式组件
render() {
// this 为 null
return <div></div>
}
}
普通组件,使用组件需要先进行实例化再挂载:new Ctor().$mount();
函数式组件,无需创建实例即可直接使用(相当于 react 中的函数组件);
他们之间唯一的区别就是 render 函数中没有 this,即没有组件状态(没有data,props等)
四,<router-view>组件的实现
1,获取渲染记录数据
在当前组件的父亲 parent 上,存在 $route 属性;
parent.$router 就是 _route,即当前匹配到的路由对象 current 属性;
/**
* router-view 函数式组件,多层级渲染
* 函数式组件特点:性能高,无需创建实例,没有 this
*/
export default {
name: 'routerView',
functional: true,// 函数式组件
render(h, { parent }) {
// 获取当前需要渲染的相关路由记录,即:this.current
let route = parent.$route;
}
}
2,记录递归深度 depth
获取到 $route 属性后,route.matched 即为全部路由匹配记录;
处理逻辑:
- app.vue 中包含 router-view 组件,渲染 Home 首页组件;
- 当点击“跳转至我的”进行路由切换时,app.vue 中的 router-view 渲染 Mine 组件;
- 当点击“我的-个人信息”进行路由切换时,mine.vue 中的 router-view 渲染 /mine/user 路由对应的组件;
所以,当访问/mine/user时,将会匹配到三条路由规则: /、/mine、/mine/user;
此时,需要按照上面的描述,依次进行渲染操作;
所以,需要通过记录深度 depth 对应到每一层 router-view 要渲染的内容;
3,处理第一层渲染
当 router-view 组件渲染时,将调用 routerView 组件的 render 方法;
此时,在当前层级(第一层)的 data 属性中,添加自定义属性 data.routerView = true;
export default {
name: 'routerView',
functional: true,
render(h, { parent, data }) {
let route = parent.$route;
let depth = 0;// 记录等级深度
data.routerView = true; // 自定义属性
}
}
第一次渲染时,根据匹配结果与层级深度depth,需要渲染 route.matched 中的第 0 项;
通过 let record = route.matched[0] 获取对应层级渲染所需的记录;
export default {
name: 'routerView',
functional: true,
render(h, { parent, data }) {
let route = parent.$route;
let depth = 0;
data.routerView = true;
// 第一层router-view 渲染第一个record 第二个router-view渲染第二个
let record = route.matched[depth]; // 获取对应层级的记录
}
}
- router-view 有可能只有一层,直接取出来;
- 若未匹配到记录,record不存在,直接返回一个空的虚拟节点 empty-vnode 叫注释节点
h() - 若匹配到记录,record有值,进行组件渲染
h(record.component),通过 record.component获取组件; - h(record.component):渲染当前组件,当组件渲染时,传入 data 数据,其中 data 包含了之前标识的routerView属性;
export default {
name: 'routerView',
functional: true,
render(h, { parent, data }) {
let route = parent.$route;
let depth = 0;
data.routerView = true;
// 根据当前层级深度获取该层级对应的路由记录,用于视图渲染
let record = route.matched[depth];
// 未匹配到路由记录,渲染空虚拟节点(empty-vnode),也叫作注释节点
if (!record) {
return h();
}
return h(record.component, data);
}
}
这样,就完成了第一层 router-view 标签的渲染;
4,处理多层 router-view 渲染
- 每次 router-view 渲染时,都会在当前 data 中设置 routerView 属性作为标记;
- render 函数中的 parent 为当前 router-view 的父标签;
- 通过循环 parent,判断
parent.$vnode.data.routerView标识,计算当前渲染的层级深度 depth; - 获取对应层级的路由记录,进行视图渲染;
相关说明:
- parent.$vnode:代表占位符的vnode;即:组件标签名的虚拟节点;
- parent._vnode 指组件的内容;即:实际要渲染的虚拟节点;
/**
* router-view 函数式组件,多层级渲染
* 函数式组件特点:性能高,无需创建实例,没有 this
*/
export default {
name: 'routerView',
functional: true, // 函数式组件
render(h, { parent, data }) {
// 获取当前需要渲染的相关路由记录,即:this.current
let route = parent.$route;
let depth = 0;// 记录等级深度
data.routerView = true; // 自定义属性
// App.vue渲染组件时,调用render函数,此时的父亲中没有 data.routerView 属性
// 在渲染第一层时,添加routerView=true标识
while (parent) { // parent 为 router-view 的父标签
// parent.$vnode:代表占位符的vnode;即:组件标签名的虚拟节点;
// parent._vnode 指组件的内容;即:实际要渲染的虚拟节点;
if (parent.$vnode && parent.$vnode.data.routerView) {
depth++;
}
parent = parent.$parent; // 更新父组件,用于循环的下一次处理
}
// 根据当前层级深度获取该层级对应的路由记录,用于视图渲染
let record = route.matched[depth];
// 未匹配到路由记录,渲染空虚拟节点(empty-vnode),也叫作注释节点
if (!record) {
return h();
}
return h(record.component, data);
}
}
以上就实现了 router-view 组件的视图更新操作;
五,结尾
本篇,介绍了 router-view 组件的实现,主要涉及以下内容:
- 函数式组件的介绍;
- router-view 组件的实现:
- 获取渲染记录;
- 标记 router-view 层级深度;
- 根据深度进行 router-view 渲染;
下一篇,介绍 vue-router 的钩子函数;