Vue 由虚拟Dom创建真实Dom过程

这篇文章简单介绍一下由虚拟Dom创建真实Dom的过程,还是以一个简单例子来进行分析:

<html>
 
<head>
<style type="text/css">

</style>
</head>
 
<body>
    <script src="./vue.js"></script>

    <div id="app">
        <div>{{message}}</div>
    </div>

<script>

const app = new Vue({
    data : {
        message : "Vnode -> node"
    }
}).$mount("#app");

</script>
</body>
</html>

之前文章分析过创建虚拟Dom的过程,这里看看从虚拟Dom到真实Dom过程。

  // public mount method
  Vue.prototype.$mount = function (
    el,
    hydrating
  ) {
    el = el && inBrowser ? query(el) : undefined;
    return mountComponent(this, el, hydrating)
  };
  
  function mountComponent (
    vm,
    el,
    hydrating
  ) {
    console.log("liubbc vm.$el: " + el.outerHTML);
    vm.$el = el;

vm.$el 这个变量后边会用到,先看看这个变量的打印:

liubbc vm.$el: <div id="app">
                            <div>{{message}}</div>
                       </div>

      updateComponent = function () {
        vm._update(vm._render(), hydrating); //vm._render()会返回新创建Vnode
      };

      Vue.prototype._update = function (vnode, hydrating) {
      var vm = this;
      var prevEl = vm.$el; 
      var prevVnode = vm._vnode; 
      var restoreActiveInstance = setActiveInstance(vm);
      vm._vnode = vnode;
      // Vue.prototype.__patch__ is injected in entry points
      // based on the rendering backend used.
      if (!prevVnode) {  
        // initial render  创建新节点
        //vm.$el 就是前面打印的div元素节点
        console.log("liubbc vm.$el: " + vm.$el.outerHTML);
        vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
      } else { 
        // updates  //更新节点
        vm.$el = vm.__patch__(prevVnode, vnode);
      }
      restoreActiveInstance();
      // update __vue__ reference
      if (prevEl) {
        prevEl.__vue__ = null;
      }
      if (vm.$el) {
        vm.$el.__vue__ = vm;
      }
      // if parent is an HOC, update its $el as well
      if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
        vm.$parent.$el = vm.$el;
      }
      // updated hook is called by the scheduler to ensure that children are
      // updated in a parent's updated hook.
    };

关键代码是这行vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)     vm.$el 作为oldVnode,vnode是以vm.$el新创建的虚拟Dom 。

新创建的vnode 里面能看到message: "Vnode -> node" 。

下面就要看看vm.__patch__这个函数了。

    // install platform patch function
    Vue.prototype.__patch__ = inBrowser ? patch : noop;

  
    return function patch (oldVnode, vnode, hydrating, removeOnly) {
      console.log("oldVnode type is: " +  oldVnode);  //[object HTMLDivElement]
      console.log("oldVnode is: " + oldVnode.outerHTML);  //<div id="app">
                                                              //  <div>{{message}}</div>
                                                                 //</div>
      if (isUndef(vnode)) {
        if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
        return
      }

      var isInitialPatch = false;
      var insertedVnodeQueue = [];

      if (isUndef(oldVnode)) {
        // empty mount (likely as component), create new root element
        isInitialPatch = true;
        createElm(vnode, insertedVnodeQueue);
      } else {        
        var isRealElement = isDef(oldVnode.nodeType); //div 的 nodeType === 1
        if (!isRealElement && sameVnode(oldVnode, vnode)) {
          // patch existing root node
          patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly);
        } else {
          if (isRealElement) {
            // mounting to a real element
            // check if this is server-rendered content and if we can perform
            // a successful hydration.
            if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
              oldVnode.removeAttribute(SSR_ATTR);
              hydrating = true;
            }
            if (isTrue(hydrating)) {
              if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
                invokeInsertHook(vnode, insertedVnodeQueue, true);
                return oldVnode
              } else {
                warn(
                  'The client-side rendered virtual DOM tree is not matching ' +
                  'server-rendered content. This is likely caused by incorrect ' +
                  'HTML markup, for example nesting block-level elements inside ' +
                  '<p>, or missing <tbody>. Bailing hydration and performing ' +
                  'full client-side render.'
                );
              }
            }
            // either not server-rendered, or hydration failed.
            // create an empty node and replace it
            oldVnode = emptyNodeAt(oldVnode); //在这里把真实的Div元素节点转换成了虚拟Dom节点
          } 
          console.log("oldVnode processed type is: " +  oldVnode); // [object Object] 

          // replacing existing element
          var oldElm = oldVnode.elm;  //oldElm 就是转换成虚拟dom节点之前的真实dom节点
          var parentElm = nodeOps.parentNode(oldElm);
          console.log("parentElm is: " + parentElm.outerHTML); //oldElm的父节点元素就是
                                                               //body节点元素

    
          // create new node  这个函数用来由虚拟dom节点创建真实dom节点,
                              //并把新节点挂载到parentElm节点下
          createElm( 
            vnode,
            insertedVnodeQueue,
            // extremely rare edge case: do not insert if old element is in a
            // leaving transition. Only happens when combining transition +
            // keep-alive + HOCs. (#4590)
            oldElm._leaveCb ? null : parentElm,
            nodeOps.nextSibling(oldElm)
          );

          // update parent placeholder node element, recursively
          if (isDef(vnode.parent)) {
            var ancestor = vnode.parent;
            var patchable = isPatchable(vnode);
            while (ancestor) {
              for (var i = 0; i < cbs.destroy.length; ++i) {
                cbs.destroy[i](ancestor);
              }
              ancestor.elm = vnode.elm;
              if (patchable) {
                for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
                  cbs.create[i$1](emptyNode, ancestor);
                }
                // #6513
                // invoke insert hooks that may have been merged by create hooks.
                // e.g. for directives that uses the "inserted" hook.
                var insert = ancestor.data.hook.insert;
                if (insert.merged) {
                  // start at index 1 to avoid re-invoking component mounted hook
                  for (var i$2 = 1; i$2 < insert.fns.length; i$2++) {
                    insert.fns[i$2]();
                  }
                }
              } else {
                console.log("liubbc createElm registerRef")
                registerRef(ancestor);
              }
              ancestor = ancestor.parent;
            }
          }

          // destroy old node
          if (isDef(parentElm)) {
            //删掉原来的节点({{message}})
            removeVnodes(parentElm, [oldVnode], 0, 0);
          } else if (isDef(oldVnode.tag)) {
            invokeDestroyHook(oldVnode);
          }
        }
      }

      invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
      return vnode.elm
    }

在关键代码处加了注释。把原来的div节点({{message}}) 先转换成虚拟dom,用新虚拟dom创建真实dom(Vnode -> node),并挂载到body节点下。最后把原来的节点删掉掉。

再看看虚拟dom创建真实dom具体过程:

    function createElm (
      vnode,
      insertedVnodeQueue,
      parentElm,
      refElm,
      nested,
      ownerArray,
      index
    ) {
      if (isDef(vnode.elm) && isDef(ownerArray)) {
        // This vnode was used in a previous render!
        // now it's used as a new node, overwriting its elm would cause
        // potential patch errors down the road when it's used as an insertion
        // reference node. Instead, we clone the node on-demand before creating
        // associated DOM element for it.
        vnode = ownerArray[index] = cloneVNode(vnode);
      }

      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)) {
        //普通元素节点
        {
          if (data && data.pre) {
            creatingElmInVPre++;
          }
          if (isUnknownElement$$1(vnode, creatingElmInVPre)) {
            warn(
              'Unknown custom element: <' + tag + '> - did you ' +
              'register the component correctly? For recursive components, ' +
              'make sure to provide the "name" option.',
              vnode.context
            );
          }
        }

        vnode.elm = vnode.ns
          ? nodeOps.createElementNS(vnode.ns, tag)
          : nodeOps.createElement(tag, vnode); //创建真实Dom节点
        setScope(vnode);

        /* istanbul ignore if */
        {
          createChildren(vnode, children, insertedVnodeQueue);
          if (isDef(data)) {    
            //在这里遍历create hooks,之前文章分析过register ref过程
            invokeCreateHooks(vnode, insertedVnodeQueue);
          }
          insert(parentElm, vnode.elm, refElm);//挂载节点到父节点
        }

        if (data && data.pre) {
          creatingElmInVPre--;
        }
      } 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);
      }
    }
发布了180 篇原创文章 · 获赞 16 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/liubangbo/article/details/103845054
今日推荐