Vue-Router 源码分析(六) router-view组件的用法及原理

router-view是一个 functional 组件,渲染路径匹配到的视图组件。<router-view> 渲染的组件还可以内嵌自己的 <router-view>,根据嵌套路径,渲染嵌套组件

它只有一个名为name的props,这个name还有个默认值,就是default,一般情况下,我们不用传递name,只有在命名视图的情况下,我们需要传递name,命名视图就是在同级展示多个视图,而不是嵌套的展示出来,

router-view组件渲染时是从VueRouter实例._route.matched属性获取需要渲染的组件,也就是我们在vue内部的this.$route.matched上获取的,举个栗子:

    <div id="app">
        <router-link to="/info/">info页</router-link>     
        <router-link to="/info/face">page页</router-link>        
        <hr/>
        <router-view></router-view>
    </div>
    <script>                   
        const info  = { template:'<div>info Page<router-view><br/></router-view></div>'}             //外层组件
        const page  = { template:'<div>face Page</div>'}                                             //内层组件
        const routes = [                                   
            {
                path:'/info/',
                component:info,
                children:[                         
                    {path:'face',component:page}        //使用了嵌套路由
                ]
            }
        ]
        const app = new Vue({                                                     
            el:'#app',
            router:new VueRouter({routes})
        })
    </script>

渲染如下:

当路由到info页时,我们在控制台打印app.$route.matched,输出如下:

 writer by:大沙漠 QQ:22969969

 当路由到page页时,我们再在控制台打印app.$route.matched,输出如下:

 可以看到matched中保存所有父子组件信息,索引从0开始,依次是顶层组件、然后是一层层下来的子组件。router-view组件内部render实现时就会读取这个matched属性的,如下:

var View = {
  name: 'RouterView',
  functional: true,                   //函数式组件
  props: {
    name: {
      type: String,
      default: 'default'
    }
  },
  render: function render (_, ref) {
    var props = ref.props;              //获取props  ;例如:{name: "default"}
    var children = ref.children;        //获取所有子节点
    var parent = ref.parent;            //父组件的引用
    var data = ref.data;

    // used by devtools to display a router-view badge
    data.routerView = true;

    // directly use parent context's createElement() function
    // so that components rendered by router-view can resolve named slots
    var h = parent.$createElement;                                              //获取父组件的$createElement函数引用  这样组件在执行render时可以用命名插槽
    var name = props.name;
    var route = parent.$route;                                                  //当前的路由地址
    var cache = parent._routerViewCache || (parent._routerViewCache = {});      //获取父组件的_routerViewCache属性,如果没有则初始化为空对象

    // determine current view depth, also check to see if the tree
    // has been toggled inactive but kept-alive.
    var depth = 0;                                      //组件嵌套的层次
    var inactive = false;                               //是否在keep-alive组件内
    while (parent && parent._routerRoot !== parent) {
      if (parent.$vnode && parent.$vnode.data.routerView) {
        depth++;
      }
      if (parent._inactive) {                             //如果parent._inactive存在
        inactive = true;                                    //则设置inactive为true
      }
      parent = parent.$parent;
    } 
    data.routerViewDepth = depth;                       //组件嵌套的层次

    // render previous view if the tree is inactive and kept-alive
    if (inactive) {
      return h(cache[name], data, children)
    }
 
    var matched = route.matched[depth];                 //从matched属性当中获取当前层次的路由对象,这里保存了需要渲染的组件,这就是上面我们通过app.$route.matched获取的对象
    // render empty node if no matched route
    if (!matched) {
      cache[name] = null;
      return h()
    }

    var component = cache[name] = matched.components[name];     //获取需要渲染的组件

    // attach instance registration hook
    // this will be called in the instance's injected lifecycle hooks
    data.registerRouteInstance = function (vm, val) {
      // val could be undefined for unregistration
      var current = matched.instances[name];
      if (
        (val && current !== vm) ||
        (!val && current === vm)
      ) {
        matched.instances[name] = val;
      }
    }

    // also register instance in prepatch hook
    // in case the same component instance is reused across different routes
    ;(data.hook || (data.hook = {})).prepatch = function (_, vnode) {
      matched.instances[name] = vnode.componentInstance;
    };

    // resolve props
    var propsToPass = data.props = resolveProps(route, matched.props && matched.props[name]);
    if (propsToPass) {
      // clone to prevent mutation
      propsToPass = data.props = extend({}, propsToPass);
      // pass non-declared props as attrs
      var attrs = data.attrs = data.attrs || {};
      for (var key in propsToPass) {
        if (!component.props || !(key in component.props)) {
          attrs[key] = propsToPass[key];
          delete propsToPass[key];
        }
      }
    }

    return h(component, data, children)                 //最后渲染该组件
  }
}

通过阅读源码,我们得知router-view通过判断当前组件的嵌套层次,然后通过这个层次从route.matches数组中获取当前需要渲染的组件,最后调用全局的$createElement来创建对应的VNode完成渲染的。

posted @ 2020-03-19 09:08  大沙漠  阅读(1668)  评论(0编辑  收藏