5.2.6 createComponent

244 阅读2分钟

1、父子组件生命周期

和dom相关的都是先子组件;

  • 加载渲染过程
父beforeCreate->父created->父beforeMount->
子beforeCreate->子created->子beforeMount->
子mounted->
父mounted
  • 子组件更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
  • 父组件更新过程
父beforeUpdate->父updated
  • 销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

2、源码解析

子组件注册

  • 全局组件放到Vue构造函数的options上,子组件实例化会合并该options
  • 最终都会放到组件实例vm.$options.components上

createComponent

  • render阶段createElement 生成vnode
  • 解析节点tag,如果为vm.$options.components的属性 或者 不是string,则会执行 vnode= createComponent()
  • 构造子类构造函数 :Ctor=baseCtor.extend(Ctor)即执行Vue.entend
  • 安装组件钩子函数:包括init、prepatch、insert、destory,分别是初次渲染、更新、activated激活、销毁的逻辑
  • 返回一个占位 vnode
export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  if (isUndef(Ctor)) {
    return
  }

  const baseCtor = context.$options._base
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor)
  }

  resolveConstructorOptions(Ctor)

  // extract props
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)

  const listeners = data.on
  data.on = data.nativeOn
  const slot = data.slot

  installComponentHooks(data)

  // 返回placeholder vnode
  const name = Ctor.options.name || tag
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )
  return vnode
}

patch

patch 的过程会调用 createElm 创建元素节点,接着执行createComponent,最终执行子组件init钩子函数

function createElm (
  vnode,
  insertedVnodeQueue,
  parentElm,
  refElm,
  nested,
  ownerArray,
  index
) {
  // ...
  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
    return
  }
  // ...
}

function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
  let i = vnode.data
  if (isDef(i)) {
    const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
    
// 执行init钩子函数
    if (isDef(i = i.hook) && isDef(i = i.init)) {
      i(vnode, false /* hydrating */)
    }

 // vnode 为组件,则留出placeholder插入
    if (isDef(vnode.componentInstance)) {
      initComponent(vnode, insertedVnodeQueue)
      insert(parentElm, vnode.elm, refElm)
      if (isTrue(isReactivated)) {
        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
      }
      return true
    }
  }
}

  • init逻辑如下,vnode.componentOptions.Ctor 对应的就是子组件的构造函数,运行Vue.prototype._init方法来合并配置、初始化数据状态
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    const child = vnode.componentInstance = createComponentInstanceForVnode(
      vnode,
      activeInstance
    )
    child.$mount(vnode.elm)
},

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

  • child.$mount(undefined, false),它最终会调用 mountComponent 方法,
  • 进而执行子组件 vm._render() 最终生成vnode;执行完 vm._render 生成 VNode 后,接下来就要执行 vm._update ,又会继续调用patch方法
  • 在完成组件的整个 patch 过程后,最后执行 insert(parentElm, vnode.elm, refElm) 完成组件的 DOM 插入,如果组件 patch 过程中又创建了子组件,那么DOM 的插入顺序是先子后父。

欢迎关注我的前端自检清单,我和你一起成长