前言
在vue的使用过程中,有一个组件,几乎是必用的,那就是router-view。它是所有组件的入口,是单页面系统的一把利剑。如果你的系统是火箭,那么router-view无疑将是这艘火箭的北斗卫星。
router-view
< router-view />是 vue-router 默认注册的全局组件。如果你从0搭建过系统的话,一定记得在Layout或者AppMain组件中有过这样一行代码:
xml
<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 是怎么设计这个组件的,以及这种设计带给我们的思考有哪些?
为了便于理解,我将这段源码粘贴下来:
javascript
// @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 表示这个组件是一个函数式组件。
arduino
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。
javascript
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
ini
// 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 循环深度遍历了。所以,我们继续往下看:
kotlin
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 意义重大,原因就在这里。
kotlin
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 会遍历路由配置,并根据当前路径匹配对应的路由记录。这个过程是自动完成的,无需手动操作。
例如:假设你的路由如下:
ini
const routes = [
{
path: '/',
name: 'Home',
component: Home,
children:[
{
path: '/about',
name: 'About',
component: About,
},
]
},
// ...
];
当访问 /about 路径时,Vue Router 会生成一个 matched数组,其中包含与当前路径 /about匹配的路由记录。在这种情况下,matched数组可能如下所示:
yaml
[
{
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,在不知不觉中逐渐向优秀的人靠齐。