【Vue源码】第十一节组件化之认识patch

createElm函数的作用之前已经讲过了,就是将VNode转为真实的DOM。

// createElm
// src/core/vdom/patch.js
function createElm (
  vnode,
  insertedVnodeQueue,
  parentElm,
  refElm,
  nested,
  ownerArray,
  index
) {
    
    
  // ...
  // 【分析createComponent】这里的createComponent跟上一节的createComponent不是同一个函数
  // 传给 createComponent 函数的 vnode 参数是组件 VNode,因此 createComponent 函数返回 true ,不会再往下执行
  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
    
    
    return
  }
      
}
// createComponent
// src/core/vdom/patch.js
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
    
    
  let i = vnode.data;
  if (isDef(i)) {
    
    
    // 和 Keep-alive 相关
    const isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
    
    // 在将组件转为VNode时有这么一行代码, const toMerge = componentVNodeHooks[key];
    // 在componentVNodeHooks中定义了init、prepatch、insert、destroy4个钩子函数 【分析init】
    // 判断 vnode.data.hook.init 是否存在,这里vnode 是一个组件 VNode,那么条件满足,并且得到 i 就是 init 钩子函数。
    if (isDef((i = i.hook)) && isDef((i = i.init))) {
    
    
      i(vnode, false /* hydrating */);
    }
    // after calling the init hook, if the vnode is a child component
    // it should've created a child instance and mounted it. the child
    // component also has set the placeholder vnode's elm.
    // in that case we can just return the element and be done.
    if (isDef(vnode.componentInstance)) {
    
    
      initComponent(vnode, insertedVnodeQueue);
      insert(parentElm, vnode.elm, refElm);
      if (isTrue(isReactivated)) {
    
    
        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
      }
      return true;
    }
  }
}
// init
// src/core/vdom/create-component.js

init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    
    
  if (
    vnode.componentInstance &&
    !vnode.componentInstance._isDestroyed &&
    vnode.data.keepAlive
  ) {
    
    
    // Keep-alive 相关
    const mountedNode: any = vnode // work around flow
    componentVNodeHooks.prepatch(mountedNode, mountedNode)
  } else {
    
    
    // activeInstance 是一个全局变量,
    // 在调用 __patch__ 前先用 prevActiveInstance 保存 activeInstance ,
    // 然后将当前实例 vm 赋给 activeInstance ,在执行完 __patch__ 后再恢复 activeInstance 原来的值
    // 分析createComponentInstanceForVnode
    const child = vnode.componentInstance = createComponentInstanceForVnode(
      vnode,
      activeInstance
    )
    // $mount 相当于执行 child.$mount(undefined, false)
    // 它最终会调用 mountComponent 方法,进而执行 vm._render() 方法
    // 分析render函数
    child.$mount(hydrating ? vnode.elm : undefined, hydrating)
  }
}
export function createComponentInstanceForVnode (
  vnode: any, // we know it's MountedComponentVNode but flow doesn't
  parent: any, // activeInstance in lifecycle state
): Component {
    
    
  const options: InternalComponentOptions = {
    
    
    _isComponent: true,
    _parentVnode: vnode,
    parent
  }
  // check inline-template render functions
  const inlineTemplate = vnode.data.inlineTemplate
  if (isDef(inlineTemplate)) {
    
    
    options.render = inlineTemplate.render
    options.staticRenderFns = inlineTemplate.staticRenderFns
  }
  // 这里的 vnode.componentOptions.Ctor 对应的就是子组件的构造函数
  // 它实际上是继承于 Vue 的一个构造器 Sub,相当于 new Sub(options)
  // 于是又执行了Vue的_init方法
  return new vnode.componentOptions.Ctor(options)
Vue.prototype._init = function (options?: Object) {
    
    
  const vm: Component = this
  // merge options
  if (options && options._isComponent) {
    
    
	// _isComponent是true
    // 分析initInternalComponent
    initInternalComponent(vm, options)
  } else {
    
    
    // ...
  }
  // 由于组件初始化的时候是不传 el 的,所以跳过,回到上上个文件
  if (vm.$options.el) {
    
    
    vm.$mount(vm.$options.el)
  } 
}
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
    
    
  const opts = vm.$options = Object.create(vm.constructor.options)
  // 主要记住下面三行代码,回到上一个文件
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode

  const vnodeComponentOptions = parentVnode.componentOptions
  opts.propsData = vnodeComponentOptions.propsData
  opts._parentListeners = vnodeComponentOptions.listeners
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag

  if (options.render) {
    
    
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}
// render
Vue.prototype._render = function (): VNode {
    
    
  const vm: Component = this
  const {
    
     render, _parentVnode } = vm.$options

  // const parentVnode = options._parentVnode
  // opts.parent = options.parent
  // opts._parentVnode = parentVnode
  
  // set parent vnode. this allows render functions to have access
  // to the data on the placeholder node.
  // 当前组件的父 VNode
  vm.$vnode = _parentVnode
  // render self
  let vnode
  try {
    
    
    // 当前组件的渲染 vnode
    vnode = render.call(vm._renderProxy, vm.$createElement)
  } catch (e) {
    
    
    // ...
  }
  // vnode 的 parent 指向了 _parentVnode,也就是 vm.$vnode,它们是一种父子的关系
  vnode.parent = _parentVnode
  // 我们知道在执行完 vm._render 生成 VNode 后,接下来就要执行 vm._update 去渲染 VNode 了
  // 分析vm._update
  return vnode
}
// src/core/instance/lifecycle.js
// vm._update 
export let activeInstance: any = null
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    
    
  const vm: Component = this
  const prevEl = vm.$el
  const prevVnode = vm._vnode
  // 这个 activeInstance 作用就是保持当前上下文的 Vue 实例
  // 在实例化子组件的过程中,它需要知道当前上下文的 Vue 实例是什么,并把它作为子组件的父 Vue 实例
  // 之前我们提到过对子组件的实例化过程先会调用 initInternalComponent(vm, options) 合并 options,
  // 把 parent 存储在 vm.$options 中,在 $mount 之前会调用 initLifecycle(vm) 方法:
  // 分析initLifecycle
  const prevActiveInstance = activeInstance
  // prevActiveInstance 和当前的 vm 是一个父子关系
  activeInstance = vm
  // 这个 vnode 是通过 vm._render() 返回的组件渲染 VNode
  vm._vnode = vnode
  // Vue.prototype.__patch__ is injected in entry points
  // based on the rendering backend used.
  if (!prevVnode) {
    
    
    // initial render
    // 最后就是调用 __patch__ 渲染 VNode 了
    // 组件在patch过程中又调用了createElm方法,又回到了本节一开始的过程 
    // createElm(vnode, insertedVnodeQueue) 往下分析createElm
    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.$parent 就是用来保留当前 vm 的父实例,并且通过 parent.$children.push(vm) 来把当前的 vm 存储到父实例的 $children 中。
export function initLifecycle (vm: Component) {
    
    
  const options = vm.$options

  // locate first non-abstract parent
  let parent = options.parent
  if (parent && !options.abstract) {
    
    
    while (parent.$options.abstract && parent.$parent) {
    
    
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }

  vm.$parent = parent
  // ...
}
// createElm
function createElm (
  vnode,
  insertedVnodeQueue,
  parentElm,
  refElm,
  nested,
  ownerArray,
  index
) {
    
    
  // createComponent返回值是 false
  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
    
    
    return
  }

  const data = vnode.data
  const children = vnode.children
  const tag = vnode.tag
  if (isDef(tag)) {
    
    
    // 先创建一个父节点占位符,然后再遍历所有子 VNode 递归调用 createElm,在遍历的过程中,如果遇到子 VNode 是一个组件的 VNode,则重复本节开始的过程,这样通过一个递归的方式就可以完整地构建了整个组件树

    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)
    }
    
    // ...
  } 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)
  }
}

总结

当我们通过createComponent创建了VNode,接下来我们会通过vm._update中的vm._patchVNode转换为真正的DOM节点。_patch的核心是_createElm函数。

_createElm中我们先使用createComponent判断返回值:

  • 如果是子组件VNode就返回true,所以在_createElm不会进行下一步;
  • 如果是普通Vnode就先创建一个父节点占位符,然后再遍历所有子 VNode 递归调用 createElm,在遍历的过程中,如果遇到子 VNode是一个组件的 VNode,则重复上一个过程。

createComponent,如果是子组件,就调用在一开始用createComponent方法中挂载的init钩子,在内部通过 createComponentInstanceForVnode 调用子构造器,创建一个Vue 的实例,_init中使用mergeOptions合并配置项然后调用 $mount 方法挂载子组件,之后再执行_render_update去渲染VNode了,然后又回到了_createElm方法中。

在完成组件的整个 patch 过程后,最后执行 insert(parentElm, vnode.elm, refElm) 完成组件的 DOM 插入,如果组件 patch 过程中又创建了子组件,那么DOM 的插入顺序是先子后父。

一个组件的 VNode 是如何创建、初始化、渲染的过程也就介绍完毕了。

这一节我们提到了mergeoptions方法,在学习这个方法前,我们先来学习一下mergeoptions中的resolveConstructorOptions函数。

猜你喜欢

转载自blog.csdn.net/qq_34086980/article/details/105934057