美文网首页
Vue源码学习(二):VueRouter

Vue源码学习(二):VueRouter

作者: HoooChan | 来源:发表于2020-07-06 19:05 被阅读0次
    <html>
    
    <head>
        <script src="https://unpkg.com/vue/dist/vue.js"></script>
        <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
    </head>
    
    <body>
        <div id="app">
            <router-view></router-view>
        </div>
    </body>
    
    <script>
        const routes = [
            { path: '/page', component: { template: '<div>/page</div>' } }
        ]
        const router = new VueRouter({
            routes
        })
    
        new Vue({
            el: '#app',
            router
        })
    </script>
    
    </html>
    

    由前一篇可以知道,在挂载的时候,<div id="app"><router-view></router-view></div>会被编译成以下渲染函数:

            (function anonymous(
            ) {
                with (this) { return _c('div', { attrs: { "id": "app" } }, [_c('router-view')], 1) }
            })
    

    _c函数是什么呢?

    在initMixin函数中定义了Vue的初始化函数_init,在初始化的过程执行了initRender(vm);:

    initRender的源码:

      function initRender (vm) {
        vm._vnode = null; // the root of the child tree
        vm._staticTrees = null; // v-once cached trees
        var options = vm.$options;
        var parentVnode = vm.$vnode = options._parentVnode; // the placeholder node in parent tree
        var renderContext = parentVnode && parentVnode.context;
        vm.$slots = resolveSlots(options._renderChildren, renderContext);
        vm.$scopedSlots = emptyObject;
        // bind the createElement fn to this instance
        // so that we get proper render context inside it.
        // args order: tag, data, children, normalizationType, alwaysNormalize
        // internal version is used by render functions compiled from templates
        vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
        // normalization is always applied for the public version, used in
        // user-written render functions.
        vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };
    
        // $attrs & $listeners are exposed for easier HOC creation.
        // they need to be reactive so that HOCs using them are always updated
        var parentData = parentVnode && parentVnode.data;
    
        /* istanbul ignore else */
        {
          defineReactive$$1(vm, '$attrs', parentData && parentData.attrs || emptyObject, function () {
            !isUpdatingChildComponent && warn("$attrs is readonly.", vm);
          }, true);
          defineReactive$$1(vm, '$listeners', options._parentListeners || emptyObject, function () {
            !isUpdatingChildComponent && warn("$listeners is readonly.", vm);
          }, true);
        }
      }
    

    可以看到这里面定义了_c方法,其实就是调用了createElement函数。

    我们再看看_c('router-view')发生了什么。

    _c('router-view')createElement(vm, 'router-view')

    经过一系列调用 来到下面的方法


    这里的tag就是'router-view'

    resolveAsset的源码:


    这里的主要工作就是取出RouterView的构造函数。

    把options打印出来,可以看到components是空的。


    这里最终通过components的原型取到RouterView,那原型里面的值是什么时候定义的呢?


    resolveAsset中的options即vm.options。这里又要回到初始化的时候调用的mergeOptions方法。

    上面一系列调用的意思就是取出Vue.options并通过Object.create()设置到vm.$options的原型中。Object.create()

    那Vue.options中的components又是什么时候定义的呢?

    全局搜索一下RouterView,在Vue.component('RouterView', View);打个断点

    这行代码是在VueRouter的install函数里面的,我们并没有主动调用Vue.use(VueRouter),那是怎么来到这里的呢?其实跟踪调用栈就可以看到下面这行代码:



    其实是VueRouter主动调用的。

    那再看看Vue.component('RouterView', View);


    image.png

    就是这里定义了Vue.options的。

    再回到前面,这时我们已经取到RouterView的构造函数了。接下来是创建虚拟节点:

    vnode = createComponent(Ctor, data, context, children, tag);
    
    ...
    
    
    if (isTrue(Ctor.options.functional)) {
        return createFunctionalComponent(Ctor, propsData, data, context, children)
    }
    
    ...
    
    var vnode = options.render.call(null, renderContext._c, renderContext);
    

    只看关键的代码,可以看到最后来到了RouterView定义的render函数,它的源码可以在VueRouter的src/components/view.js中找到。
    直接看它的最后一行代码:

    return h(component, data, children)
    

    这个h函数是什么呢?可以看看这个函数的前面:

          var parent = ref.parent;
          ...
          var h = parent.$createElement;
    

    其实就是$createElement函数。

    component又是怎么取到的呢?



    这里先取到route,那parent的$route又是什么是定义的呢?这里要回到VueRouter的install函数。

    function install (Vue) {
        if (install.installed && _Vue === Vue) { return }
        install.installed = true;
    
        _Vue = Vue;
    
        var isDef = function (v) { return v !== undefined; };
    
        var registerInstance = function (vm, callVal) {
          var i = vm.$options._parentVnode;
          if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
            i(vm, callVal);
          }
        };
    
        Vue.mixin({
          beforeCreate: function beforeCreate () {
            if (isDef(this.$options.router)) {
              this._routerRoot = this;
              this._router = this.$options.router;
              this._router.init(this);
              Vue.util.defineReactive(this, '_route', this._router.history.current);
            } else {
              this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
            }
            registerInstance(this, this);
          },
          destroyed: function destroyed () {
            registerInstance(this);
          }
        });
    
        Object.defineProperty(Vue.prototype, '$router', {
          get: function get () { return this._routerRoot._router }
        });
    
        Object.defineProperty(Vue.prototype, '$route', {
          get: function get () { return this._routerRoot._route }
        });
    
        Vue.component('RouterView', View);
        Vue.component('RouterLink', Link);
    
        var strats = Vue.config.optionMergeStrategies;
        // use the same hook merging strategy for route hooks
        strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created;
      }
    

    可以看到这里通过mixin在组件beforeCreate的时候设置根节点的_router,_route,再设置子组件的_routerRoot,之后给Vue的原型定义两个变量_router和_route让他们从_routerRoot取值。
    注意_route的定义是响应式的:

    Vue.util.defineReactive(this, '_route', this._router.history.current);
    

    意思就是在渲染的时候调用到_route的会被依赖收集,当_route变的时候会通知改组件。RouterView就是这样更新的。

    拿到route就可以拿到对应的component了,之后就是调用$createElement创建虚拟节点。

    _route是什么时候更新的呢?可以在defineReactive$$1定义的setter里面打个断点,看调用栈可以找到_route被设置值的地方:


    来看看HashHistory的原理。
    首先HashHistory在初始化的时候给window加上popstate和hashchange的监听

          var eventType = supportsPushState ? 'popstate' : 'hashchange';
          window.addEventListener(
            eventType,
            handleRoutingEvent
          );
    

    当监听到地址变化的时候就执行transitionTo然后confirmTransition,这其中经过一系列的调用,包括路由守卫的处理,到最后调用updateRoute(route):



    cb就是在下面的代码定义的:

        history.listen(function (route) {
          this$1.apps.forEach(function (app) {
            app._route = route;
          });
        });
    

    至此_route修改,然后通知RouterView进行更新。

    相关文章

      网友评论

          本文标题:Vue源码学习(二):VueRouter

          本文链接:https://www.haomeiwen.com/subject/zrpgqktx.html