vue組件頁面渲染的基本流程

html:
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8"/>
    <title>組件頁面渲染的基本過程</title>
</head>
<body>
    <div id="app"></div>
</body>
</html>
main.js:
import Vue from "vue";
import Home from "./home.vue";

new Vue({
    el: "#app",
    template: "<Home/>",
    components: { Home }
});
home.vue
<template>
    <div class="home">
        <a>{{text}}</a>
    </div>
</template>
<script>
    export default {
        name: "home",
        data() {
            return {
                text: '測試'
            };
        },
        mounted() {
            
        },
        methods: {
        }
    };
</script>

1.項目運行編譯時,home.vue中的template會被vue-loader編譯成render函數,並添加到組件選項對象裏。組件經過components引用該組件選項,建立組件(Vue實例),渲染頁面(多個組件引用同一組件時,引用的是相同的對象,解釋data爲何要是函數)。main.js根組件template轉化成render函數是由vue插件中的編譯器實現的。javascript

// 編譯器生成的render函數:
var render = function() {
    var _vm = this
    var _h = _vm.$createElement
    var _c = _vm._self._c || _h
    return _c(
        "div",
        { staticClass: "home" },
        [
            _c("a",
                [_vm._v(_vm._s(_vm.text))]
            )
        ]
    )
}
// 編譯後的組件選項對象:
{
    beforeCreate: []
    beforeDestroy: []
    data: function() {}
    methods: {}
    mounted: function() {}
    name: "home"
    render: function() {}
    staticRenderFns: []
    __file: "src/page/home/index.vue"
    _compiled: true
}

2.組件渲染頁面時會先調用render函數,render函數返回組件內標籤節點(VNode實例)。每一個標籤(包括文本和組件標籤等)會建立一個節點,先建立子標籤的節點,再將它添加父節點的children數組中,造成與標籤結構相同的樹形結構。css

// 節點構造函數
var VNode = function VNode (
  tag,
  data,
  children,
  text,
  elm,
  context,
  componentOptions,
  asyncFactory
) {
  this.tag = tag;// 標籤名
  this.data = data;// 節點數據,包括節點的生命週期函數
  this.children = children;// 當節點是原生標籤節點,保存它的子節點
  this.text = text;// 爲文本節點或者註釋節點時的文本內容
  this.elm = elm;// 節點對應的DOM,組件節點則對應的是該組件內原生根標籤DOM
  this.ns = undefined;
  this.context = context;// 節點對應的組件
  this.fnContext = undefined;
  this.fnOptions = undefined;
  this.fnScopeId = undefined;
  this.key = data && data.key;// key值
  this.componentOptions = componentOptions;// 緩存組件標籤信息:包括組件名稱,組件選項、標籤上的props、標籤上的事件、以及組件標籤內的子節點。
  this.componentInstance = undefined;// 組件標籤節點對應的組件(Vue實例)
  this.parent = undefined;// 當節點是組件內根標籤節點,保存它的組件標籤節點
  this.raw = false;
  this.isStatic = false;
  this.isRootInsert = true;
  this.isComment = false;
  this.isCloned = false;
  this.isOnce = false;
  this.asyncFactory = asyncFactory;
  this.asyncMeta = undefined;
  this.isAsyncPlaceholder = false;
};
// 調用render函數,生成組件模板對應的節點
Vue.prototype._render = function () {
    var vm = this;
    var ref = vm.$options;
    var render = ref.render;
    var _parentVnode = ref._parentVnode;
    ...
    vm.$vnode = _parentVnode;
    // render self
    var vnode;
    ...
    currentRenderingInstance = vm;
    vnode = render.call(vm._renderProxy, vm.$createElement);//返回組件模板造成節點(組件內根標籤節點)
    ...
    currentRenderingInstance = null;
    ...
    vnode.parent = _parentVnode;// 設置組件根標籤節點的parent爲當前組件節點。
    return vnode
};
// 根據標籤建立節點
function _createElement (
  context,
  tag,
  data,
  children,
  normalizationType
) {
    if (isDef(data) && isDef((data).__ob__)) {
        ...
        return createEmptyVNode()
    }
    ...
    if (!tag) {
        return createEmptyVNode()
    }
    ...
    if (typeof tag === 'string') {
        vnode = new VNode(
            config.parsePlatformTagName(tag), data, children,
            undefined, undefined, context
        );
        } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {/* 從組件實例option的components中尋找該組件標籤信息(組件對象) */
        vnode = createComponent(Ctor, data, context, children, tag);// 組件標籤節點
        } else {
        vnode = new VNode(
            tag, data, children,
            undefined, undefined, context
        );
        }
    } else {
        vnode = createComponent(tag, data, context, children);// 標籤節點
    }
    if (Array.isArray(vnode)) {
        return vnode
    } else if (isDef(vnode)) {
        if (isDef(ns)) { applyNS(vnode, ns); }
        if (isDef(data)) { registerDeepBindings(data); }
        return vnode
    } else {
        return createEmptyVNode()
    }
}

3.若是標籤是組件標籤,經過components獲取的組件選項對象,並使用extend方法生成組件的構造函數,將構造函數和組件選項保存在組件標籤節點上。html

4.render函數生成組件內標籤對應的節點,並設置根節點的parent指向當前組件節點。將節點做爲新節點,傳入到patch方法中,組件頁面初始更新時,不存在舊節點,直接根據新節點建立DOM。vue

// 組件頁面更新
Vue.prototype._update = function (vnode, hydrating) {
    var vm = this;
    var prevEl = vm.$el;
    var prevVnode = vm._vnode;
    var restoreActiveInstance = setActiveInstance(vm);
    vm._vnode = vnode;
    ...
    if (!prevVnode) {// 組件初次渲染
    // initial render
        vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);// 將組件內根標籤DOM賦值給實例的$el屬性
    } else {
    // updates
        vm.$el = vm.__patch__(prevVnode, vnode);// 將組件內根標籤DOM賦值給實例的$el屬性
    }
    restoreActiveInstance();
    ...
  };

5.在patch方法中,根據節點建立DOM,並在節點上保存它的DOM引用,再根據節點的children值,建立子節點的DOM,再添加到父節點的DOM中,完成組件的渲染。java

// 根據節點類型(原生標籤或者組件標籤),建立組件或者DOM
function createElm (
    vnode,
    insertedVnodeQueue,
    parentElm,
    refElm,
    nested,
    ownerArray,
    index
  ) {
   ...
    vnode.isRootInsert = !nested; // for transition enter check
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {// 若是vnode是組件節點,建立組件
      return
    }

    var data = vnode.data;
    var children = vnode.children;
    var tag = vnode.tag;
    if (isDef(tag)) {
      ...
      vnode.elm = vnode.ns // 建立DOM
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode);
      setScope(vnode);// 添加DOM屬性,構造css做用域
      {
        createChildren(vnode, children, insertedVnodeQueue);// 建立子節點的DOM
        if (isDef(data)) {
          invokeCreateHooks(vnode, insertedVnodeQueue);
        }
        insert(parentElm, vnode.elm, refElm);// 添加父節點的DOM中
      }
        ...
    } else if (isTrue(vnode.isComment)) {// 註釋節點
      vnode.elm = nodeOps.createComment(vnode.text);
      insert(parentElm, vnode.elm, refElm);// 添加DOM
    } else {// 文本節點
      vnode.elm = nodeOps.createTextNode(vnode.text);
      insert(parentElm, vnode.elm, refElm);// 添加DOM
    }
  }

6.若是節點包含組件構造器信息(組件節點),會先使用構造器建立組件,調用組件render方法,執行以上操做,生成組件內標籤對應的節點,再根據節點生成DOM,並將根標籤節點的DOM保存在組件上,而後添加到父節點的DOM上,完成組件的渲染。DOM添加到頁面的過程是從下往上依次添加,DOM添加到父級DOM中,父級DOM添加到它的父級DOM中,迭代添加,最後將最上級的DOM添加到頁面。node

// 生成組件,並將組件內根標籤DOM添加到父級DOM中:
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    var i = vnode.data;
    if (isDef(i)) {
        var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
        if (isDef(i = i.hook) && isDef(i = i.init)) {
            i(vnode, false /* hydrating */);// 調用節點生命週期函數init,生成組件
        }
        if (isDef(vnode.componentInstance)) {
            initComponent(vnode, insertedVnodeQueue);
            insert(parentElm, vnode.elm, refElm);// 組件內根標籤DOM添加到父級DOM中
            if (isTrue(isReactivated)) {
            reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
            }
            return true
        }
    }
}
// 在節點生命週期init建立組件
function init (vnode, hydrating) {
    ...
    var child = vnode.componentInstance = createComponentInstanceForVnode(// 建立組件
    vnode,
    activeInstance// 父組件
    );
    child.$mount(hydrating ? vnode.elm : undefined, hydrating);//渲染頁面
}

組件頁面渲染的基本流程圖示.png

相關文章
相關標籤/搜索