likes
comments
collection
share

【源码解析】 RouterView组件的实现原理

作者站长头像
站长
· 阅读数 47

前言

大家好,上一篇文章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的分享就到这里了,欢迎大佬们指点!!!