Vue.js源码阅读、六

组件的初始化渲染

在之前render的时候,如果createElement的第一个参数tag是一个组件,就会调用createComponent创建组件的VNode,那么接下来看组建的patch过程有什么不一样的地方

createPatchFunction中的createElm函数有一个分支逻辑:

    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
      return
    }

那么看一下createComponent的定义:

  function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    let i = vnode.data
    if (isDef(i)) {
      const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
      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
      }
    }
  }

如果vnode.data.hook.init是有定义的,那么调用这个init函数:init(vnode, false)。这个init函数是创建组件VNode时添加的钩子函数init,prepatch,insert,destory之一。

  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },

if分支的逻辑是keep-alive的情况,else分支首先调用了createComponentInstanceForVnode,它用new vnode.componentOptions.Ctor创建了一个VueComponent实例,Ctor是通过Vue.extend继承Vue构造函数得到的。

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
  }
  return new vnode.componentOptions.Ctor(options)
}

这个组件实例的options中有几个特别的属性:_isComponent: true表示它是个组件,_parentVnoode是组件VNode,parent是当前的activeInstance。 组件的构造函数调用了Vue.prototype._init方法

Vue.prototype._init = function (options?: Object) {
  const vm: Component = this
  // merge options
  if (options && options._isComponent) {
    // optimize internal component instantiation
    // since dynamic options merging is pretty slow, and none of the
    // internal component options needs special treatment.
    initInternalComponent(vm, options)
  } else {
    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
      options || {},
      vm
    )
  }
  // ...
  initLifecycle(vm)
// ...
  if (vm.$options.el) {
    vm.$mount(vm.$options.el)
  } 
}

由于options._isComponent === true,这里执行了initInternalComponent方法,这个方法把子组件的一些配置合并到子组件的$options中。在调用initLifecyle时,把子组件实例添加到了parent.$children中。最后由于vm.$option.el没有定义,$mount在这里是不会被调用的。

回到componentVNodeHooks里的init钩子函数,createComponentInstanceForVnode之后调用了child.$mount(undefinded, false),这个$mount就是Vue.prototype.$mount, 它最终调用了core/instance/lifecycle.js中的mountComponent方法来挂载这个子组件实例,最后调用了Vue.prototype._init方法

vm._update(vm._render(), hydrating)

我们知道组件的根节点只有一个,所以vm._render()执行了组件的render函数,返回组件根节点的VNode。然后调用_update方法

export let activeInstance: any = null
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    // ...
    const prevActiveInstance = activeInstance
    activeInstance = vm
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    activeInstance = prevActiveInstance
  }

这个activeInstance是定义在core/instance/lifecycle.js的全局变量,export let activeInstance: any = null。作用是保存当前激活的Vue实例。实际上Vue的初始化是一个深度优先遍历的过程,每一个子组件都需要知道他的父组件$parent是什么,activeInstance用来保存当前vm的父实例。在_update的时候,用prevAcitveInstance保存上一次的activeInstance_update结束后再恢复,这样就可以在深度优先遍历的过程中保存组件实例之间的父子关系。

_update中又执行了patch的过程,如果又遇到子组件就重复上面的逻辑。
渲染后的组件根DOM结点保存到vm.$el。返回到createElm中的createComponent

    let i = vnode.data
    if (isDef(i)) {
      const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
      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`钩子之后,`vnode.componetInstaence`就应该是一个组件的Vue实例了,调用`insert`将组件的DOM结点插入DOM

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