vue中的router-view组件是如何生成的?

前言

在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
  1. 引入依赖

引入 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,所以这个 参数可以忽略

  1. 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,在不知不觉中逐渐向优秀的人靠齐。

相关推荐
奋斗吧程序媛2 分钟前
删除VSCode上 origin/分支名,但GitLab上实际上不存在的分支
前端·vscode
IT女孩儿12 分钟前
JavaScript--WebAPI查缺补漏(二)
开发语言·前端·javascript·html·ecmascript
醒了就刷牙18 分钟前
黑马Java面试教程_P9_MySQL
java·mysql·面试
m0_748256562 小时前
如何解决前端发送数据到后端为空的问题
前端
请叫我飞哥@2 小时前
HTML5适配手机
前端·html·html5
黑客老陈3 小时前
面试经验分享 | 北京渗透测试岗位
运维·服务器·经验分享·安全·web安全·面试·职场和发展
@解忧杂货铺4 小时前
前端vue如何实现数字框中通过鼠标滚轮上下滚动增减数字
前端·javascript·vue.js
F-2H6 小时前
C语言:指针4(常量指针和指针常量及动态内存分配)
java·linux·c语言·开发语言·前端·c++
gqkmiss6 小时前
Chrome 浏览器插件获取网页 iframe 中的 window 对象
前端·chrome·iframe·postmessage·chrome 插件
m0_748247559 小时前
Web 应用项目开发全流程解析与实战经验分享
开发语言·前端·php