chenfaxiang / chenfaxiang.github.io

Issues、学习记录。
https://www.chenfx.com.cn
5 stars 1 forks source link

13 - 组件化 (createComponent 的实现) - part 4 #13

Open chenfaxiang opened 6 years ago

chenfaxiang commented 6 years ago

安装组件钩子函数

// install component management hooks onto the placeholder node
installComponentHooks(data)

Vue.js 利用 VNode 在 patch 的流程中对外暴露了不同时机的钩子函数,方便使用者利用钩子函数做不同的事情;installComponentHooks() 方法的定义如下:

function installComponentHooks (data: VNodeData) {
  const hooks = data.hook || (data.hook = {})
  for (let i = 0; i < hooksToMerge.length; i++) {
    const key = hooksToMerge[i]
    const existing = hooks[key]
    // 初始化对应的钩子函数
    const toMerge = componentVNodeHooks[key]
    // 判断钩子函数是否已经存在
    if (existing !== toMerge && !(existing && existing._merged)) {
     // 已经存在则进行合并
      hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
    }
  }
}

在初始化一个 Component 类型的 VNode 的过程中出现了 componentVNodeHooksmergeHook 函数,如下:

// inline hooks to be invoked on component VNodes during patch
const componentVNodeHooks = {
  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)
    }
  },

  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    const options = vnode.componentOptions
    const child = vnode.componentInstance = oldVnode.componentInstance
    updateChildComponent(
      child,
      options.propsData, // updated props
      options.listeners, // updated listeners
      vnode, // new parent vnode
      options.children // new children
    )
  },

  insert (vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true
      callHook(componentInstance, 'mounted')
    }
    if (vnode.data.keepAlive) {
      if (context._isMounted) {
        // vue-router#1212
        // During updates, a kept-alive component's child components may
        // change, so directly walking the tree here may call activated hooks
        // on incorrect children. Instead we push them into a queue which will
        // be processed after the whole patch process ended.
        queueActivatedComponent(componentInstance)
      } else {
        activateChildComponent(componentInstance, true /* direct */)
      }
    }
  },

  destroy (vnode: MountedComponentVNode) {
    const { componentInstance } = vnode
    if (!componentInstance._isDestroyed) {
      if (!vnode.data.keepAlive) {
        componentInstance.$destroy()
      } else {
        deactivateChildComponent(componentInstance, true /* direct */)
      }
    }
  }
}

const hooksToMerge = Object.keys(componentVNodeHooks)

// 合并钩子函数的操作
function mergeHook (f1: any, f2: any): Function {
  const merged = (a, b) => {
    // flow complains about extra args which is why we use any
    f1(a, b)
    f2(a, b)
  }
  merged._merged = true
  return merged
}

在前面的 installComponentHooks 执行完之后,就到了实例化 VNode 部分,如下:

// 实例化 VNode
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )

  return vnode

通过 new VNode 实例化一个 vnode 并返回;

综上,在 createComponent 的过程中,有三个比较重要的步骤就是构造子类构造函数、安装组件钩子函数、实例化 vnode,而最终返回了 vnode