Vue2.x源码学习笔记(十三)——installComponentHooks

211 阅读1分钟
const hooksToMerge = Object.keys(componentVNodeHooks)
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
    }
  }
}
function mergeHook (f1: any, f2: any): Function {
  const merged = (a, b) => {
    f1(a, b)
    f2(a, b)
  }
  merged._merged = true
  return merged
}

在数据对象上设置hook对象,从data.hook对象中获取key对应的已存在的方法。合并用户传递的hook方法和框架自带的hook方法,合并的过程就是分别执行两个方法。

componentVNodeHooks

componentVNodeHooks对象上存放了init,prepatch,insert和destroy钩子函数。

const componentVNodeHooks = {
    init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    ......
    },
    prepatch(oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    ......
    },
    insert (vnode: MountedComponentVNode) {
    ......
    },
    destroy (vnode: MountedComponentVNode) {
    ......
    }
}

init

init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      const mountedNode: any = vnode
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  }

如果组件是被keep-alive包裹的组件,那就调用prepatch钩子函数,传入的新旧VNode参数都是同一个VNode。否则调用createComponentInstanceForVnode创建组件实例,将组件实例赋值给vnode的componentInstance属性。然后执行组件的$mount方法,进入挂载阶段,接下来就是通过编译器得到render函数,接着走挂载、patch这条路,直到组件渲染到页面。

createComponentInstanceForVnode

function createComponentInstanceForVnode (
  vnode: any, 
  parent: any,
): Component {
  const options: InternalComponentOptions = {
    _isComponent: true,
    _parentVnode: vnode,
    parent
  }
  const inlineTemplate = vnode.data.inlineTemplate
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render
    options.staticRenderFns = inlineTemplate.staticRenderFns
  }
  return new vnode.componentOptions.Ctor(options)
}

配置好options配置项,_isComponent表示当前实例是组件实例,_parentVnode表示当前的vnode,parent表示当前激活的组件实例。检查内联模版渲染函数,获取render和staticRenderFns。

然后new创建实例:这里的vnode.componentOptions.Ctor对应的是组件的构造函数,new的时候会调用组件实例的_init方法。

prepatch

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

获取新VNode的配置,获取老VNode的组件实例,然后用新的VNode配置更新旧的VNode上的各种属性。

insert

insert (vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true
      callHook(componentInstance, 'mounted')
    }
    if (vnode.data.keepAlive) {
      if (context._isMounted) {
        queueActivatedComponent(componentInstance)
      } else {
        activateChildComponent(componentInstance, true /* direct */)
      }
    }
  },

如果组件未挂载,则调用mounted声明周期钩子,然后处理处理 keep-alive 组件的异常情况。

destroy

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

如果组件实例没有被销毁并且没有被keepAlive组件包裹,就调用$destroy。如果被keepalive组件包裹的话就调用deactivated钩子函数,负责让组件失活,不销毁组件实例,从而缓存组件的状态。