【VueRouter 源码学习】第九篇 - router-view 组件的实现

1,015 阅读3分钟

这是我参与8月更文挑战的第25天,活动详情查看:8月更文挑战


一,前言

上篇,介绍了$route$routerrouter-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等)

Vue官网:函数式组件的介绍


四,<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 的钩子函数;