1. 程式人生 > >Vue源碼後記-vFor列表渲染(3)

Vue源碼後記-vFor列表渲染(3)

undefined ++ 源碼 blog back war 什麽 tns check

  這一節肯定能完!

  

  經過DOM字符串的AST轉化,再通過render變成vnode,最後就剩下patch到頁面上了。

  render函數跑完應該是在這裏:

    function mountComponent(vm, el, hydrating) {
        vm.$el = el;
        if (!vm.$options.render) {
            vm.$options.render = createEmptyVNode; {
                // warning
            }
        }
        
// beforeMount var updateComponent; /* istanbul ignore if */ if ("development" !== ‘production‘ && config.performance && mark) { updateComponent = function() { // dev render }; } else { updateComponent = function
() { vm._update(vm._render(), hydrating); }; } // code... // mounted return vm }

  vm._render()會生成一個vnode看,接下來調用_update渲染頁面,如下:

    Vue.prototype._update = function(vnode, hydrating) {
        var vm = this;
        // beforeUpdate

        //
code... if (!prevVnode) { // initial render vm.$el = vm.__patch__( vm.$el, vnode, hydrating, false /* removeOnly */ , vm.$options._parentElm, vm.$options._refElm ); } else { // updates vm.$el = vm.__patch__(prevVnode, vnode); } // code... }; Vue$3.prototype.__patch__ = inBrowser ? patch : noop; var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules }); function createPatchFunction(backend) { var i, j; var cbs = {}; var modules = backend.modules; var nodeOps = backend.nodeOps; for (i = 0; i < hooks.length; ++i) { // hook... } // fn... return function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) { // code... if (isUndef(oldVnode)) { // component... } else { var isRealElement = isDef(oldVnode.nodeType); if (!isRealElement && sameVnode(oldVnode, vnode)) { // patch existing root node } else { // SSR or hydrating var oldElm = oldVnode.elm; var parentElm$1 = nodeOps.parentNode(oldElm); createElm( vnode, insertedVnodeQueue, oldElm._leaveCb ? null : parentElm$1, nodeOps.nextSibling(oldElm) ); //code... } } invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch); return vnode.elm } }

  由於是初始化頁面,所有在update的過程中,oldVNode被設置為空的div虛擬DOM,然後與生成的虛擬DOM進行替換。

  核心細節在上述代碼中的createElm函數:

    // vnode => 生成的vnode
    // insertedVnodeQueue => []
    // parentElm => body
    // refElm => #text
    // nested => undefined
    function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested) {
        vnode.isRootInsert = !nested; // for transition enter check
        if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
            return
        }

        var data = vnode.data;
        var children = vnode.children;
        var tag = vnode.tag;
        if (isDef(tag)) {
            // pre...

            vnode.elm = vnode.ns ?
                nodeOps.createElementNS(vnode.ns, tag) :
                // 調用這個生成一個tag標簽
                nodeOps.createElement(tag, vnode);
            setScope(vnode);

            {
                // 處理子節點
                // 子節點是5個vnode組成的數據 因此會循環調用本函數
                createChildren(vnode, children, insertedVnodeQueue);
                if (isDef(data)) {
                    // 生成DOM節點的屬性
                    invokeCreateHooks(vnode, insertedVnodeQueue);
                }
                // 將子節點插入到父節點中
                // 處理到最外層節點 頁面會渲染
                insert(parentElm, vnode.elm, refElm);
            }

            if ("development" !== ‘production‘ && data && data.pre) {
                inPre--;
            }
        } else if (isTrue(vnode.isComment)) {
            vnode.elm = nodeOps.createComment(vnode.text);
            insert(parentElm, vnode.elm, refElm);
        } else {
            vnode.elm = nodeOps.createTextNode(vnode.text);
            insert(parentElm, vnode.elm, refElm);
        }
    }

  其實,這個普通的patch沒有區別,只是由於是多個標簽,所以會有兄弟元素,在插入節點會調用insertBefore進行插入,最後5個a標簽依次插入生成的div,然後div插入body標簽完成頁面渲染。

  雖然循環生成a標簽以及其屬性比較麻煩,但是由於整個標簽是一次性插入body中,所以對於性能也沒有什麽影響。

  完事,確實沒什麽好說的,至於v-if、v-show那些,有空一次性寫完。

Vue源碼後記-vFor列表渲染(3)