Vue.js源码阅读、五

初始化渲染

之前已经看到,mount时调用了vm._update(vm._render(), hydrating),其中vm._render()是把调用了vm.$options中的render函数创建了VNode。那么vm._update的第一个参数就是一个VNode。

vm._update方法的定义是在core/instance/lifecycle.js中:

export function lifecycleMixin (Vue: Class) {
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const prevActiveInstance = activeInstance
    activeInstance = 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)
    }
    activeInstance = prevActiveInstance
    // 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._vnode也就是prevVnodeundefined,所以会调用

vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)

否则的话就会调用

vm.$el = vm.__patch__(prevVnode, vnode)

vm._patch__方法在web和weex平台的定义是不一样的,web平台中的定义在platforms/web/runtime/index.js

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

这里分了是否是在浏览器环境中两种情况,服务端渲染中,它是一个空函数。
patch定义在plaforms/web/runtime/patch.js:


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

createPatchFunctioncore/vdom/patch.js中:

const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
...
export function createPatchFunction (backend) {
  let i, j
  const cbs = {}

  const { modules, nodeOps } = backend

  for (i = 0; i < hooks.length; ++i) {
    cbs[hooks[i]] = []
    for (j = 0; j < modules.length; ++j) {
      if (isDef(modules[j][hooks[i]])) {
        cbs[hooks[i]].push(modules[j][hooks[i]])
      }
    }
  }
  ...
  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, 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 ' +
                '

, or missing . 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 } }

createPatchFunction 方法的代码非常的多,定义了很多辅助函数,最后返回了一个patch方法。
patch方法接收4个参数,oldVnode 表示旧的 VNode 节点,它也可以不存在或者是一个 DOM 对象; vnode表示render后返回的VNode; hydrating表示是否是服务端渲染。

第一个for循环里定义了不同阶段要调用的回调函数,modules表示处理vnode.data中不同的属性的方法,包括attrs, props, class等。比如处理data.attrs的方法定义在platforms/web/runtime/modules/attrs.js,表示在create和update时调用updateAttrs方法

export default {
  create: updateAttrs,
  update: updateAttrs
}
···

在Vue根实例首次渲染的时候,`oldVnode`的值就是`vm.$el`,也就是根实例挂载到的那个DOM结点。那么就会先调用`oldVnode = emptyNodeAt(oldVnode)`,先把`oldVnode`转换为VNode对象。然后调用`createElm`方法。

·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__) {...
  } 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)
}

}

在`if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) `这里,如果vnode是组件vnode,就会调用组件的渲染逻辑,然后返回,否则继续执行下面的逻辑。
`nodeOps`中包含了标准的DOM操作函数的封装,调用`createElement`创建DOM结点,
用`invokeCreateHooks(vnode, insertedVnodeQueue)`处理data中的选项,用`insert`方法插入DOM,`insert`方法就相当于`parentElm.insertBefore(vnode.elm, refElm)`
如果包含子节点,就用`createChildren`递归地创建子节点。

你可能感兴趣的:(Vue.js源码阅读、五)