【源码解析】 RouterView组件的实现原理
前言
大家好,上一篇文章RouterLink我们介绍了RouterLink组件的使用方法及源码分析,我们知道原来RouterLink最终还是会被渲染成a
标签,与a标签不同的是通过RouterLink可以实现在不重新加载页面的情况下可以实现url的切换
。那么除了RouterLink用于实现路由切换的组件外,还有一个用于承载页面渲染的RouterView组件,接下来我们就一起来学习一下RouterVeiw,看看它又干了哪些事。
RouterView的用法及用途
RouterView主要用于承载路由(url)对应的组件的渲染显示,我们可以将RouterView放在任何地方以适应我们项目的布局。RouterView的用法也非常简单,只需要将RouterView组件直接以标签的形式放在任何我们想要放置的地方即可,这样对应url的组件就会渲染显示在这里了。
<router-view></router-view>
另外我们现在用的RouterView属于是默认视图,除了默认视图外还有一种命名视图,也就是说RouterView可以接收一个name属性,name值一般是组件名称,命名视图主要用于显示固定的组件。比如有的时候我们想在同一个页面中同时展示多个视图,但不是嵌套展示而是同级展示,例如我们想创建一个布局,有 sidebar
(侧导航) 和 main
(主内容) 两个视图,这个时候我们就可以使用命名视图了。
<router-view name="Sidebar"></router-view>
<router-view ></router-view>
如上代码我们添加了2个router-view,其中一个添加了name属性并赋值为Sidebar
而另一个没有name属性我们称之为默认视图,这时带name属性的router-view就只会渲染显示Sidebar组件,而其它组件则会显示在默认视图中,同时由于同一个路由要对应多个组件,那么路由在定义时也要稍作改动
routes: [
{
path: '/',
components: { // 注意这里是components 而不是component
default: Home,
// Sidebar: Sidebar 的缩写,它们与 `<router-view>` 上的 `name` 属性匹配
Sidebar,
},
},
],
关于RouterView的用法及用途就介绍到这里了,其实还是挺简单的,下面我们来看下RouterView的源码,看看它是如何实现这些功能的。
源码解读
与前面我们学习过的RouterLink一样,RouterView也是一个函数式组件,同样RouterView组件的定义实际上是一个包含了name、functional、props和render四个属性的View对象。
var View = {
name: 'RouterView',
functional: true,
props: {
name: {
type: String,
default: 'default'
}
},
render: function render (_, ref) {
var props = ref.props;
var children = ref.children;
var parent = ref.parent;
var data = ref.data;
data.routerView = true;
var h = parent.$createElement;
var name = props.name;
var route = parent.$route;
var cache = parent._routerViewCache || (parent._routerViewCache = {});
var depth = 0;
var inactive = false;
while (parent && parent._routerRoot !== parent) {
var vnodeData = parent.$vnode ? parent.$vnode.data : {};
if (vnodeData.routerView) {
depth++;
}
if (vnodeData.keepAlive && parent._directInactive && parent._inactive) {
inactive = true;
}
parent = parent.$parent;
}
data.routerViewDepth = depth;
if (inactive) {
var cachedData = cache[name];
var cachedComponent = cachedData && cachedData.component;
if (cachedComponent) {
...
return h(cachedComponent, data, children)
} else {
return h()
}
}
var matched = route.matched[depth];
var component = matched && matched.components[name];
if (!matched || !component) {
cache[name] = null;
return h()
}
// cache component
cache[name] = { component: component };
data.registerRouteInstance = function (vm, val) {
...
}
data.hook.init = function (vnode) {
...
};
return h(component, data, children)
}
};
- name:name属性的值为
RouterView
,这个就是组件的名称,我们在全局注册的时候用的就是这个名称 - functional:属性值为true,用于指示这是一个功能性组件
- props:props中只定义了一个name属性,默认值为
default
,这个属性就用于标识视图是默认视图还是命名视图 - render:render是一个函数,所有的关于RouterView组件的核心操作都在这里了
-
render函数接收2个参数:
_
和ref
,其中_
就是createElement函数,而ref实际上就是当前所渲染的RouterView的实例对象 -
在render内部首先从当前RouterView实例对象ref上解析出props、children、parent和data属性
-
接着下面则直接使用了当前RouterView组件的父级上下文的createElement函数,路由信息和routerview的缓存信息。代码中给出的说明意思就是:
直接使用父级上下文中的createElement函数,以便被router-view渲染的组件能够解析命名插槽
-
通过while来获取当前RouterView的深度,因为RouterView是可以进行多层嵌套使用的,同时检测树中除了被keepAlive缓存节点是否已经被转换为非活动状态
-
判断如果树处于非活动状态并且在keepAlive中,则渲染前一个视图
- 在渲染组件时首先检查要渲染的组件是否被缓存,如果有缓存则直接从缓存中取出并调用h函数进行渲染
- 如果没有缓存则直接调用h函数渲染一个空视图
-
代码继续向下,根据当前的路由信息到路由的matched对象中找到路由对应的组件
- 如果没有匹配到对应到组件,则直接调用h函数渲染一个空组件
- 如果匹配到了对应到组件,则将组件进行缓存
-
然后这里定义了一个路由实例注册的钩子函数
registerRouteInstance
,该函数会在路由实例的注入生命周期钩子中被调用,也就是我们之前分析install源码时会在混入的beforeCreate生命周期函数中调用 -
下面又定义了个
init
函数,目的是为了让keep-alive组件在路由变化时被激活 -
最后通过调用h函数(createElement)将组件虚拟dom转换为真实dom进行渲染(未缓存)
-
总结
本次分享我们学习了RouterView的使用方法及用途,解读了其源码实现,简单总结就是:
RouterView组件仅接收一个name自定义属性,主要用于区分命名视图和默认视图,然后在render函数中根据当前路由信息及自定义的name属性去匹配对应的组件,匹配时会优先检查是否被缓存,如果被缓存则直接从缓存中获取组件并调用h函数进行渲染,如果没有缓存则先将组件进行缓存然后在通过h函数进行渲染,最终实现对应组件的显示
好了本次关于RouterView的分享就到这里了,欢迎大佬们指点!!!
转载自:https://juejin.cn/post/7110085803904073764