返回

一叶知秋,Vue 源码阅读(五):组件虚拟 DOM 渲染为真实 DOM 全解析

前端

从组件虚拟 DOM 到真实 DOM

我们在上一篇文章中介绍了如何将普通虚拟 DOM 转为真实的 DOM,本文将继续介绍如何将组件类型的虚拟 DOM 转为真实的 DOM。

创建组件实例

如果 vnode 是组件类型的,则在生成 DOM 时,会调用 createComponent 方法。我们可以看到,该方法首先判断 vnode.data 上是否有 hook 和 init 方法,如果有,则会调用这些方法。

function createComponent (vnode, parentElm, childElm, refElm) {
  const vnodeComponentOptions = vnode.data.componentOptions
  let hooks = vnode.data.hook
  // 对组件 vnode 初始化
  if (isDef(hooks)) {
    // 调用 vnode 钩子函数
    for (let i = 0; i < hooks.length; i++) {
      invokeCreateHooks(hooks[i], parentElm, vnode, childElm)
    }
  }

  const name = vnode.data.ref
  if (isDef(name)) {
    // 创建组件引用
    createRef(parentElm, refElm, vnode)
  }

  // 判断是否有 init 钩子函数
  if (isDef(vnodeComponentOptions)) {
    setupComponent(vnode, parentElm, childElm, refElm)
  }
}

初始化组件实例

在创建组件实例后,会调用 setupComponent 方法来初始化组件实例。该方法首先会调用 vnodeComponentOptions.beforeCreate 方法,然后创建组件的实例,并调用 vnodeComponentOptions.created 方法。

function setupComponent (vnode, parentElm, childElm, refElm) {
  let componentOptions = vnode.data.componentOptions
  let hook = vnode.data.hook

  // 调用 beforeCreate 生命周期钩子函数
  if (isDef(componentOptions.beforeCreate)) {
    componentOptions.beforeCreate(
      vnode,
      parentElm,
      childElm
    )
  }

  // 创建组件实例
  const child = new vnodeComponentOptions.Ctor(
    // 传入组件 vnode 的数据部分
    options = resolveAsyncComponentOptions(
      componentOptions,
      resolveVNodeComponentOptions
    )
  )

  // 调用 created 生命周期钩子函数
  if (isDef(hook)) {
    if (isDef(hook.create)) {
      hook.create(child, parentElm, vnode, childElm)
    }
  } else if (isDef(componentOptions.created)) {
    componentOptions.created.call(child)
  }
}

挂载组件实例

在初始化组件实例后,会调用 mountComponent 方法来挂载组件实例。该方法首先会调用 vnodeComponentOptions.beforeMount 方法,然后调用 vnodeComponentOptions.mounted 方法。

function mountComponent (vnode, parentElm, childElm, refElm) {
  const options = vnode.componentOptions
  if (isDef(options.beforeMount)) {
    options.beforeMount(vnode, parentElm, childElm, refElm)
  }
  // 调用 mount 生命周期钩子函数
  if (isDef(options.mounted)) {
    options.mounted(vnode, parentElm, childElm, refElm)
  }
}

更新组件实例

在组件实例挂载后,如果组件的 props 或 state 发生变化,则会调用 updateComponent 方法来更新组件实例。该方法首先会调用 vnodeComponentOptions.beforeUpdate 方法,然后调用 vnodeComponentOptions.updated 方法。

function updateComponent (prevVnode, vnode) {
  const componentOptions = vnode.data.componentOptions
  if (isDef(componentOptions)) {
    const prevVnodeComponentOptions = prevVnode.data.componentOptions
    if (isDef(componentOptions.beforeUpdate)) {
      // 调用 beforeUpdate 生命周期钩子函数
      componentOptions.beforeUpdate(
        prevVnode,
        vnode,
        prevVnode.context
      )
    }
    // 调用 updated 生命周期钩子函数
    if (isDef(componentOptions.updated)) {
      componentOptions.updated(prevVnode, vnode, prevVnode.context)
    }
  }
}

总结

本文介绍了如何将组件类型的虚拟 DOM 转为真实的 DOM。我们首先介绍了如何创建组件实例,然后介绍了如何初始化、挂载和更新组件实例。希望本文能够帮助大家更好地理解 Vue 源码。