详解vue中router-view组件的生成原理
作者:唐朝人
前言
在vue的使用过程中,有一个组件,几乎是必用的,那就是router-view。它是所有组件的入口,是单页面系统的一把利剑。如果你的系统是火箭,那么router-view无疑将是这艘火箭的北斗卫星。
router-view
< router-view />是 vue-router 默认注册的全局组件。如果你从0搭建过系统的话,一定记得在Layout或者AppMain组件中有过这样一行代码:
<transition name="fade-transform" mode="out-in"> <div style="height: 100%"> // 所有组件的入口 <router-view /> </div> </transition>
我们一直在用,但是有没有想过:这个组件是干嘛用的?为什么注册为全局组件,又为什么是所有页面组件的入口?它……究竟有什么用?
带着这样的疑问,我打开了 vue-router 的gitHub切换到 vue2 分支
然后 在 src->components->view.ts 中找到了 router-view 的相关代码:
下面来分析一下这个组件的源码,看一下 vue-router 是怎么设计这个组件的,以及这种设计带给我们的思考有哪些?
为了便于理解,我将这段源码粘贴下来:
// @ts-nocheck import { Component } from 'vue' const View: Component = { name: 'RouterView', functional: true, props: { name: { type: String, default: 'default', }, }, render(_, { children, parent, data, props }) { data.routerView = true const h = parent.$createElement const route = parent.$route // determine current view depth, also check to see if the tree // has been toggled inactive but kept-alive. let depth = 0 // let inactive = false // @ts-ignore while (parent && parent._routerRoot !== parent) { const vnodeData = parent.$vnode && parent.$vnode.data if (vnodeData) { // @ts-ignore if (vnodeData.routerView) { depth++ } // if (vnodeData.keepAlive && parent._inactive) { // inactive = true // } } parent = parent.$parent } data.routerViewDepth = depth const matched = route.matched[depth] if (!matched) return h() const component = matched.components[props.name] return h(component, data, children) }, } export default View
- 引入依赖
引入 vue 中的 component 组件,然后注册一个 name 为 RouterView 的组件,这里的functional:true 表示这个组件是一个函数式组件。
import { Component } from 'vue' const View: Component = { name: 'RouterView', functional: true, props: { name: { type: String, default: 'default', }, }
组件内部定义了一个 props,接受参数 name。一般情况下,router-view 很少传 name,所以这个 参数可以忽略
- render函数的参数通过解构赋值拿到组件内部属性
我们在 render 中拿到了 children、parent、data、props 等属性。在 parent 中我们取到了 $createElement 内部方法,以及 route 路由信息,然后定义了一个 depth 变量,并初始赋值为 0。
render(_, { children, parent, data, props }) { data.routerView = true const h = parent.$createElement const route = parent.$route // determine current view depth, also check to see if the tree // has been toggled inactive but kept-alive. let depth = 0
这个 depth 意义重大!一开始我也在疑惑,vueRouter 在设计时为什么要有这 个 depth,这个变量有什么用?
后来仔细翻看了代码,发现 depth 记录了每一条路由的索引,然后又将该索引赋值给$vnode->data->routerViewDepth
// has been toggled inactive but kept-alive. let depth = 0 // let inactive = false // @ts-ignore while (parent && parent._routerRoot !== parent) { const vnodeData = parent.$vnode && parent.$vnode.data if (vnodeData) { // @ts-ignore if (vnodeData.routerView) { depth++ } // if (vnodeData.keepAlive && parent._inactive) { // inactive = true // } } parent = parent.$parent } data.routerViewDepth = depth const matched = route.matched[depth]
注意看:routerViewDepth 就是这个 depth 值。
可是,routerViewDepth 只是记录了这个值而已,depth 难道没有其他作用了吗?当然不是,如果仅仅是记录一个索引值,但就没必要大动干戈地调用 while 循环深度遍历了。所以,我们继续往下看:
const matched = route.matched[depth] if (!matched) return h() const component = matched.components[props.name] return h(component, data, children)
在当前路由下,有个 route 对象,里面记录了页面的基本信息。其中有一个 matched 属性至关重要,它是一个 components 集合。也就是说,在当前路由下的所有组件(包括父级 组件以及兄弟组件),都存在于这个 matched 集合里。
而 depth 是当前路由的索引,也就是 matched 集合中的 key。通过 depth 我们能对应到路由中对应好的 component。
所以我们前面为什么说depth 意义重大,原因就在这里。
const matched = route.matched[depth] if (!matched) return h() const component = matched.components[props.name] return h(component, data, children)
最后,源码中增加了一层判断,如果没有找到 matched 则返回一个空的 vnode,如果找到了则返回对应的 component,最后完成渲染。
matched
matched 集合是从路由记录树中根据当前路由的路径生成的。当发生路由导航时,Vue Router 会遍历路由配置,并根据当前路径匹配对应的路由记录。这个过程是自动完成的,无需手动操作。
例如:假设你的路由如下:
const routes = [ { path: '/', name: 'Home', component: Home, children:[ { path: '/about', name: 'About', component: About, }, ] }, // ... ];
当访问 /about 路径时,Vue Router 会生成一个 matched数组,其中包含与当前路径 /about匹配的路由记录。在这种情况下,matched数组可能如下所示:
[ { name:'Home', alias: xx, beforeEnter: (...), components: Object, enteredCbs: (...), instances: (...), matchAs: (...), meta: (...), xxx }, { alias: xx, name:'About', beforeEnter: (...), components: Object, enteredCbs: (...), instances: (...), matchAs: (...), meta: (...), xxx }, ]
matched数组的顺序是根据路由配置的嵌套关系确定的,父级路由记录在数组中的顺序靠前。
注意,matched数组是在路由导航过程中生成的,因此在路由导航之前或导航到未定义的路径时,matched 数组可能为空。
可以通过在路由组件中访问 this.$route.matched 来获取当前路由的匹配路由记录数组。
总结
虽然现在已经进入了 vue3 的世界,但是 vue2 源码中的诸多设计仍然值得我们借鉴,技术始终服务于业务。相信现在不少公司仍然有大量的 vue2 项目在维护,推翻 shi 山,深度重构不是一朝一夕的事。对于源码,我们可以抱着学习的态度去看待,或者看成是一种兴趣。源码看多了,你的代码不会再 shi,在不知不觉中逐渐向优秀的人靠齐。
以上就是详解vue中的router-view组件是如何生成的的详细内容,更多关于vue router-view组件生成的资料请关注脚本之家其它相关文章!