vue源码分析之异步组件

本文深入探讨Vue.js中组件的创建过程,特别是涉及工厂函数和Promise在异步组件加载中的应用。当组件值为对象时,执行Vue.extend()构造组件;为函数时,执行resolveAsyncComponent,此函数处理异步加载,利用Promise配合webpack的语法糖实现组件延迟加载。在组件加载过程中,先返回一个占位符节点,待异步加载完成后更新实际组件。
摘要由CSDN通过智能技术生成

一、工厂函数

1当组件执行_render函数转换成虚拟VNode时遇到组件时会执行createComponent()函数,如下:

function createComponent (      //第4184行  创建组件Vnode     
  Ctor,                                     //Ctor:组件的构造函数
  data,                                     //data:数组 
  context,                                  //context:Vue实例
  children,                                 //child:组件的子节点
  tag
) {
  if (isUndef(Ctor)) {
    return
  }

  var baseCtor = context.$options._base;

  // plain options object: turn it into a constructor
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor);
  }

  // if at this stage it's not a constructor or an async component factory,
  // reject.
  if (typeof Ctor !== 'function') {
    if (process.env.NODE_ENV !== 'production') {
      warn(("Invalid Component definition: " + (String(Ctor))), context);
    }
    return
  }

  // async component
  var asyncFactory;
  if (isUndef(Ctor.cid)) {                    //如果Ctor.cid为空,那么Ctor就是一个函数,表明这是一个异步组件
    asyncFactory = Ctor;                                                //获取异步组件的函数
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context);      //执行resolveAsyncComponent()函数
    if (Ctor === undefined) {                                           //如果Ctor是个空的,调用该函数返回一个空的注释节点
      // return a placeholder node for async component, which is rendered
      // as a comment node but preserves all the raw information for the node.
      // the information will be used for async server-rendering and hydration.
      return createAsyncPlaceholder(                                    
        asyncFactory, 
        data,
        context,
        children,
        tag
      )
    }
  }

  /*略*/
  return vnode
}

对于一个组件来说,比如Vue.component(component-name,obj|func),组件的值可以是一个对象,也可以是一个函数,如果是对象,则注册时会执行Vue.extend()函数,如下:

if (type === 'component' && isPlainObject(definition)) {    //第4866行 注册组件时,如果组件是个对象,则执行Vue.extend()
    definition.name = definition.name || id;
    definition = this.options._base.extend(definition);
}

去构造子组件的基础构造函数,此时会在构造函数上新增一个cid属性(在4789行),所以我们这里通过cid来判断该组件是否为一个函数。

回到主线,接着执行resolveAsyncComponent()函数,工厂函数相关的如下:

function resolveAsyncComponent (      //第2283行  异步组件   factory:异步组件的函数 baseCtor:大Vue  context:当前的Vue实例
  factory,
  baseCtor,
  context
) {
  if (isTrue(factory.error) && isDef(factory.errorComp)) {
    return factory.errorComp
  }

  if (isDef(factory.resolved)) {                          //工厂函数异步组件第二次执行这里时会返回factory.resolved
    return factory.resolved
  }

  if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
    return factory.loadingComp
  }

  if (isDef(factory.contexts)) {
    // already pending
    factory.contexts.push(context);
  } else {
    var contexts = factory.contexts = [context];                //将context作为数组保存到contexts里,也就是当前Vue实例
    var sync = true;  

    var forceRender = function () {                //遍历contexts里的所有元素                                         下一个tick执行到这里
      for (var i = 0, l = contexts.length; i < l; i++) {      //依次调用该元素的$forceUpdate()方法 该方法会强制渲染一次
        contexts[i].$forceUpdate();
      }
    };

    var resolve = once(function (res) {                         //定义一个resolve函数
      // cache resolved
      factory.resolved = ensureCtor(res, baseCtor);
      // invoke callbacks only if this is not a synchronous resolve
      // (async resolves are shimmed as synchronous during SSR)
      if (!sync) {
        forceRender();
      }
    });

    var reject = once(function (reason) {                       //定义一个reject函数
      "development" !== 'production' && warn(
        "Failed to resolve async component: " + (String(factory)) +
        (reason ? ("\nReason: " + reason) : '')
      );
      if (isDef(factory.errorComp)) {
        factory.error = true;
        forceRender();
      }
    });

    var res = factory(resolve, reject);                       //执行factory()函数

    if (isObject(res)) {
      /*高级组件的逻辑*/
    }

    sync = false;
    // return in case resolved synchronously
    return factory.loading
      ? factory.loadingComp
      : factory.resolved
  }
}

resolveAsyncComponent内部会定义一个resolve和reject函数,然后执行factory()函数,factory()就是我们在main.js里给HelloWorld组件定义的函数,函数内会执行require函数,由于require()是个异步操作,所以resolveAsyncComponent就会返回undefined

回到resolveAsyncComponent,我们给factory()函数的执行下一个断点,如下:

 可以看到返回一个undefined,最后resolveAsyncComponent()也会返回undefined,回到createComponent()函数,由于返回的是undefined,则会执行createAsyncPlaceholder()去创建一个注释节点,渲染后对应的DOM节点树如下:

可以看到对于工厂函数来说,组件完全加载时对应的DOM节点是一个注释节点 

在下一个tick等require()加载成功后就会执行resolve(res)函数,也就是在resolveAsyncComponent()内定义的resolve函数,

resolve函数会将结果保存到工厂函数的resolved属性里(也就是组件的定义)然后执行的forceRender()函数,也就是上面标记的蓝色的注释对应的代码

再次重新渲染执行到resolveAsyncComponent的时候此时局部变量factory.resolved存在了,就直接返回该变量, 如下:

 二、Promise加载

Promise()比较简单,可以认为是工厂函数扩展成语法糖的知识,他主要是可以很好的配合webpack的语法糖,webpack的import的语法糖就是返回一个promise对象,Vue实际上做异步组件也是为了配合Webpack的语法糖来实现Promise()的趋势。

import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

Vue.component('HelloWorld',()=>import('./components/HelloWorld'))

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

和工厂函数一样,也会执行两次resolveAsyncComponent,下一个tick的逻辑是一样的,不一样的是触发resolve()的逻辑不通,如下:

function resolveAsyncComponent (          //异步组件
  factory,  
  baseCtor,
  context
) {
  if (isTrue(factory.error) && isDef(factory.errorComp)) {
    return factory.errorComp
  }

  if (isDef(factory.resolved)) {                              //第一次执行到这里时factory.resolved也不存在
    return factory.resolved
  }

    /*略*/
    var res = factory(resolve, reject);                       //我们这里返回一个含有then的对象

    if (isObject(res)) {                                      
      if (typeof res.then === 'function') {                      //如果res是一个函数,即Promise()方式加载时
        // () => Promise
        if (isUndef(factory.resolved)) {                            //如果factory.resolved不存在
          res.then(resolve, reject);                                  //用then方法指定resolve和reject的回调函数
        }
      } else if (isDef(res.component) && typeof res.component.then === 'function') {
        /**/
      }
    }

    sync = false;
    // return in case resolved synchronously
    return factory.loading
      ? factory.loadingComp
      : factory.resolved
  }
}

例子里执行到factory()后返回的res对象如下:

等到加载成功后就会执行resolve了,后面的步骤和工厂函数的流程是一样的。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值