天天看點

Vue中的元件從初始化到挂載經曆了什麼

下面的所有解析都以這段代碼為基準:

new Vue({
  el: "#app",
  render: h => h(AppSon)
});
複制代碼           

複制

其中 AppSon 就是元件,它是一個對象:

const AppSon = {
  name: "app-son",
  data() {
    return {
      msg: 123
    };
  },
  render(h) {
    return h("span", [this.msg]);
  }
};
複制代碼           

複制

這樣一段代碼,在 Vue 内部元件化的流程順序:

  1. $createElement

    ,其實 render 接受的參數 h 就是

    this.$createElement

    的别名
  2. createElement

    ,做一下參數的整理,就進入下一步
  3. _createElement

    ,比較關鍵的一步,在這個方法裡會判斷元件是

    span

    這樣的 html 标簽,還是使用者寫的自定義元件。
  4. createComponent

    ,生成元件的 vnode,安裝一些 vnode 的生命周期,傳回 vnode

其實,render 函數最終傳回的就是

vnode

流程解析

$createElement

調用

createElement

方法,第一個參數是 vm 執行個體自身,剩餘的參數原封不動的透傳。

vm.$createElement = function(a, b, c, d) {
  return createElement(vm, a, b, c, d, true);
};
複制代碼           

複制

createElement

function createElement (
  // 上一步傳進來的vm執行個體,在哪個元件的render裡調用,context就是哪個元件的執行個體。
  context,
  // 在例子中,就是AppSon這個對象
  tag,
  // 可以傳入props等交給子元件的選項
  data,
  // 子元件中間的内容
  children,
  ...
)
複制代碼           

複制

之後有一個判斷

if (typeof tag === "string") {
  // html标簽流程
} else {
  // 元件化流程
  vnode = createComponent(tag, data, context, children);
}
複制代碼           

複制

createComponent

接受的四個參數就是上文的方法傳進去的

createComponent

function createComponent(
  // 還是上文中的tag,本文中是AppSon對象
  Ctor,
  // 下面的都一緻
  data,
  context,
  children
) {
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor);
  }

  // 給vnode安裝一些生命周期函數(注意這裡是vnode的生命周期,而不是created那些元件聲明周期)
  installComponentHooks(data);

  var vnode = new VNode(
    "vue-component-" + Ctor.cid + (name ? "-" + name : ""),
    data,
    undefined,
    undefined,
    undefined,
    context,
    {
      Ctor: Ctor,
      propsData: propsData,
      listeners: listeners,
      tag: tag,
      children: children
    },
    asyncFactory
  );

  return vnode;
}
複制代碼           

複制

下面有一個邏輯

if (isObject(Ctor)) {
  Ctor = baseCtor.extend(Ctor);
}
複制代碼           

複制

其中

baseCtor.extend(Ctor)

就可以暫時了解為 Vue.extend,這是一個全局共用方法,從名字也可以看出它主要是做一些繼承,讓子元件的也擁有父元件的一些能力,這個方法傳回的是一個新的構造函數。

元件對象最終都會用 extend 這個 api 變成一個元件構造函數,這個構造函數繼承了父構造函數 Vue 的一些屬性

extend 函數具體做了什麼呢?

createComponent / Vue.extend

Vue.extend = function(extendOptions) {
  extendOptions = extendOptions || {};
  // this在這個例子其實就是Vue。
  var Super = this;

  // Appson這個元件的構造函數
  var Sub = function VueComponent(options) {
    // 這個_init就是調用的Vue.prototype._init
    this._init(options);
  };

  // 把Vue.prototype生成一個
  // { __proto__: Vue.prototype }這樣的對象,
  // 直接指派給子元件構造函數的prototype
  // 此時子元件構造函數的原型鍊上就可以拿到Vue的原型鍊的屬性了
  Sub.prototype = Object.create(Super.prototype);
  Sub.prototype.constructor = Sub;

  // 合并Vue.option上的一些全局配置
  Sub.options = mergeOptions(Super.options, extendOptions);
  Sub["super"] = Super;

  // 拷貝靜态函數
  Sub.extend = Super.extend;
  Sub.mixin = Super.mixin;
  Sub.use = Super.use;

  // 傳回子元件的構造函數
  return Sub;
};
複制代碼           

複制

到了這一步,我們一開始定義的 Appson 元件對象,已經變成了一個函數,可以通過 new AppSon()來生成一個元件執行個體了,并且元件配置對象被合并到了

Sub.options

這個構造函數的靜态屬性上。

createComponent / installComponentHooks

installComponentHooks

這個方法是為了給 vnode 上加入一些生命周期函數,

其中有一個

init

生命周期,這個周期後面被調用的時候再講解。

createComponent / new VNode

可以看出,主要是生成 vnode 的執行個體,并且指派給

vnode.componentInstance

,并且調用

$mount

方法挂載 dom 節點,注意這個

init

生命周期此時還沒有調用。

到這為止

render

的流程就講完了,現在我們擁有了一個

vnode

節點,它有一些關鍵的屬性

  1. vnode.componentOptions.Ctor: 上一步

    extend

    生成的子元件構造函數。
  2. vnode.data.hook: 裡面儲存了

    init

    等 vnode 生命周期方法
  3. vnode.context: 調用$createElement 的是哪個執行個體,這個 context 就是誰。

$mount

最外層的元件調用了

$mount

後,元件在初次渲染的時候其實是遞歸去調用

createElm

的,而

createElm

中會去調用元件 vnode 的

init

鈎子。

if (isDef((i = i.hook)) && isDef((i = i.init))) {
  i(vnode);
}
複制代碼           

複制

然後就會走進 vnode 的

init

生命周期的邏輯

const child = (vnode.componentInstance = createComponentInstanceForVnode(
  vnode,
  activeInstance
));
child.$mount(vnode.elm);
複制代碼           

複制

createComponentInstanceForVnode

:

createComponentInstanceForVnode (
  vnode: any,
  parent: any,
): Component {
  const options: InternalComponentOptions = {
    // 标記這是一個元件節點
    _isComponent: true,
    // Appson元件的vnode
    _parentVnode: vnode,
    // 目前正在活躍的父元件執行個體,在本例中就是根Vue執行個體
    // new Vue({
    //   el: "#app",
    //   render: h => h(AppSon)
    // });
    parent
  }

  return new vnode.componentOptions.Ctor(options)
}
複制代碼           

複制

可以看出,最終調用元件構造函數,然後調用

\_init

方法,它接受到的

options

不再是

{
  data() {

  },
  props: {

  },
  methods() {

  }
}
複制代碼           

複制

這樣的傳統 Vue 對象了,而是

{
    _isComponent: true,
    _parentVnode: vnode,
    parent,
  }
複制代碼           

複制

這樣的一個對象,然後_init 内部會針對這樣特征的對象,調用

initInternalComponent

做一些特殊的處理, 這裡有一個疑惑點,那剛剛子元件聲明的 data 那些選項哪去了呢? 其實是被儲存在

Ctor.options

裡了。

然後在

initInternalComponent

中,把子元件構造函數上儲存的 options 再轉移到

vm.$options.__proto__

上。

var opts = (vm.$options = Object.create(vm.constructor.options));
複制代碼           

複制

之後生成了子元件的執行個體後,又會調用

child.$mount(vnode.elm)

,繼續的去遞歸這個初始化的過程。