美文网首页程序员
vue VNode映射真实dom 整体流程

vue VNode映射真实dom 整体流程

作者: 臣以君纲 | 来源:发表于2019-01-19 19:29 被阅读4次

    上一节我们提到,VNode 在创建完成后,会通过 _update 方法将虚拟dom通过dom操作,append,insert等等插入到真实dom树上,我们先来看_update

     Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
        const vm: Component = this
        const prevEl = vm.$el
        const prevVnode = vm._vnode
        const 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 = 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.
      }
    

    _update方法是在我们执行_init方法时执行一些列初始化操作时定义的, update方法会在两种情况下被调用,一是new Vue初始化的时候,还有一种情况就是当我们改变data数据,页面重新渲染时,然后调用patch方法,我们进入patch

    export const patch: Function = createPatchFunction({ nodeOps, modules })
    

    可以看到patch 方法是由一createPathFunction 方法返回的,这个方法传入了两个参数,nodeOps,modules, 摘取一部分nodeOps内容

    export function createElementNS (namespace: string, tagName: string): Element {
      return document.createElementNS(namespaceMap[namespace], tagName)
    }
    
    export function createTextNode (text: string): Text {
      return document.createTextNode(text)
    }
    
    export function createComment (text: string): Comment {
      return document.createComment(text)
    }
    

    可以看到,里面都是一些原生dom操作的封装,摘取modules一部分内容

    export function addClass (el: HTMLElement, cls: ?string) {
      /* istanbul ignore if */
      if (!cls || !(cls = cls.trim())) {
        return
      }
    
      /* istanbul ignore else */
      if (el.classList) {
        if (cls.indexOf(' ') > -1) {
          cls.split(whitespaceRE).forEach(c => el.classList.add(c))
        } else {
          el.classList.add(cls)
        }
      } else {
        const cur = ` ${el.getAttribute('class') || ''} `
        if (cur.indexOf(' ' + cls + ' ') < 0) {
          el.setAttribute('class', (cur + cls).trim())
        }
      }
    }
    

    可以看到,是一些对原生dom特性控制的封装,,以及一些辅助函数,
    下面我们回到createPatchFunction,createPatchFunction 方法中首先定义了好多的辅助函数,最后返回了一个函数,即patch,我么来看下这个patch

    return function patch (oldVnode, vnode, hydrating, removeOnly) {
        if (isUndef(vnode)) {
          if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
          return
        }
    
        let isInitialPatch = false
        const insertedVnodeQueue = []
    
        if (isUndef(oldVnode)) {
          // empty mount (likely as component), create new root element
          isInitialPatch = true
          createElm(vnode, insertedVnodeQueue)
        } else {
          const isRealElement = isDef(oldVnode.nodeType)
          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 if (process.env.NODE_ENV !== 'production') {
                  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)
            }
    
            // replacing existing element
            const oldElm = oldVnode.elm
            const parentElm = nodeOps.parentNode(oldElm)
    
            // create new node
            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)) {
              let ancestor = vnode.parent
              const patchable = isPatchable(vnode)
              while (ancestor) {
                for (let i = 0; i < cbs.destroy.length; ++i) {
                  cbs.destroy[i](ancestor)
                }
                ancestor.elm = vnode.elm
                if (patchable) {
                  for (let i = 0; i < cbs.create.length; ++i) {
                    cbs.create[i](emptyNode, ancestor)
                  }
                  // #6513
                  // invoke insert hooks that may have been merged by create hooks.
                  // e.g. for directives that uses the "inserted" hook.
                  const insert = ancestor.data.hook.insert
                  if (insert.merged) {
                    // start at index 1 to avoid re-invoking component mounted hook
                    for (let i = 1; i < insert.fns.length; i++) {
                      insert.fns[i]()
                    }
                  }
                } else {
                  registerRef(ancestor)
                }
                ancestor = ancestor.parent
              }
            }
    
            // destroy old node
            if (isDef(parentElm)) {
              removeVnodes(parentElm, [oldVnode], 0, 0)
            } else if (isDef(oldVnode.tag)) {
              invokeDestroyHook(oldVnode)
            }
          }
        }
    
        invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
        return vnode.elm
      }
    

    重点,执行了createElm方法,这个方法定义于传入的modules辅助函数中, 这个方法才是真实dom操作的核心所在,我们进入createElm

    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
        }
    
        const data = vnode.data
        const children = vnode.children
        const tag = vnode.tag
        if (isDef(tag)) {
          if (process.env.NODE_ENV !== 'production') {
            if (data && data.pre) {
              creatingElmInVPre++
            }
            if (isUnknownElement(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)
          setScope(vnode)
    
          /* istanbul ignore if */
          if (__WEEX__) {
            // in Weex, the default insertion order is parent-first.
            // List items can be optimized to use children-first insertion
            // with append="tree".
            const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
            if (!appendAsTree) {
              if (isDef(data)) {
                invokeCreateHooks(vnode, insertedVnodeQueue)
              }
              insert(parentElm, vnode.elm, refElm)
            }
            createChildren(vnode, children, insertedVnodeQueue)
            if (appendAsTree) {
              if (isDef(data)) {
                invokeCreateHooks(vnode, insertedVnodeQueue)
              }
              insert(parentElm, vnode.elm, refElm)
            }
          } else {
            createChildren(vnode, children, insertedVnodeQueue)
            if (isDef(data)) {
              invokeCreateHooks(vnode, insertedVnodeQueue)
            }
            insert(parentElm, vnode.elm, refElm)
          }
    
          if (process.env.NODE_ENV !== 'production' && 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)
        }
      }
    

    这个函数名字我们可以大胆猜想,作者把这一部映射的操作且分成了很多逻辑,函数有点多,名字不太好起了,createElm,,,
    可以看到这里大量用了原生的api,来创建dom元素,createChildren ,对VNode的子元素进行递归调用传递父元素,来构建真实的dom树,最后,通过调用insert方法,即dom的insert方法,将整个dom树一次性插入到dom树中,大家可以通过打断点的方法,完整查看整个_update流程

    相关文章

      网友评论

        本文标题:vue VNode映射真实dom 整体流程

        本文链接:https://www.haomeiwen.com/subject/dvjzdqtx.html