解析 vue3 实例创建过程源码

1,728 阅读3分钟
  • 开始 mount 挂载
    • 创建 vNode
    • render 方法渲染 vNode
      • 创建 instance 实例
      • 开始 init 初始化
        • 优先处理 setup 上下文
          • 优先初始化 props 和 slots
          • 然后检测是否有 render 方法,有则不需要 compile 否则需要 compile 获取 render 方法
          • 然后再适配 vue2.0 语法初始化 props(前面已经初始化完毕),inject,methods, data,computed,watch
        • init 初始化完毕
      • 开始创建实例 update 方法
        • update 的 effect 回调函数有两套逻辑 mountComponent 和 updateComponent
        • 两者都先进行 render 然后再 patch
  • mount 挂载完毕

响应式数据挂载后触发执行收集 update 回调函数依赖,当响应式数据有变动会触发执行依赖走 updateComponent 逻辑重新渲染

mount

mount:

mount(rootContainer: HostElement, isHydrate?: boolean): any {
  if (!isMounted) {
    const vnode = createVNode(/*...*/) // 首先创建 vnode
    // ...
    if (isHydrate && hydrate) {/*...*/} else {
      render(vnode, rootContainer) // render 方法渲染 vNode 这里调用 patch 方法
    }
    // ...

render 方法渲染 vNode 调用 patch 方法

patch:

const patch: PatchFn = (/*...*/) => {
    // ...
    const { type, ref, shapeFlag } = n2 // type vNode 类型根据不同类型做相应处理
    switch (type) {
    //   ...
      default:
        if (shapeFlag & ShapeFlags.ELEMENT) {
        //   ...
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
          processComponent(/*...*/) // process 方法一般有两种处理方式 首次加载 和 更新
          // ...

instance

processComponent 中如果是首次加载则执行 mountComponent,createComponentInstance 方法创建组件实例:

  const mountComponent: MountComponentFn = (/*...*/) => {
    //   instance 通过 createComponentInstance 方法创建
    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(/*...*/))
    // ...

createComponentInstance 创建实例

export function createComponentInstance(/*...*/) {
    //   ...
  const instance: ComponentInternalInstance = {
    //   初始化实例 state emit lifecycle hooks 等数据
  }
  if (__DEV__) {
    instance.ctx = createRenderContext(instance) // 创建并绑定上下文 ctx
  } else {
    instance.ctx = { _: instance }
  }
  //   ...
  return instance
}

init

回到 mountComponent 开始 init 初始化:

const mountComponent: MountComponentFn = (/*...*/) => {
  // ...
  if (__DEV__) {
    startMeasure(instance, `mount`) // start mount
  }
  // ...
  if (__DEV__) {
    startMeasure(instance, `init`) // start init
  }
  setupComponent(instance) // 开始创建 setup 上下文
  // ...

setupComponent 开始处理组件 setup 上下文,在开始之前先处理 props 的初始化:

export function setupComponent(/*...*/) {
    // ...
  initProps(instance, props, isStateful, isSSR) // 开始初始化 prop
  // initProps 会执行校验、创建 shallowReactive
  initSlots(instance, children) // 开始初始化 slot
  // ...
  setupStatefulComponent(instance, isSSR) // 这里开始处理 setup 实例方法
  // ...
  return setupResult
}

执行 setup 函数或触发 finishComponentSetup:

function setupStatefulComponent(/*...*/) {
    // ...
  const { setup } = Component
  if (setup) {
    // 这里有一堆处理 setup 上下文的方法
  } else {
    finishComponentSetup(instance, isSSR)
  }
}

最后触发 finishComponentSetup 方法:

  • 检测实例是否有 render 方法,没有则 compile 这里就是 runtime compile 运行时编译,如果有 render 方法则不需要运行时编译
  • 适配 vue2.0 语法,初始化顺序是 props,inject,methods, data,computed,watch
function finishComponentSetup(/*...*/) {
    // ...
  if (__NODE_JS__ && isSSR) {
    //   ...
  } else if (!instance.render) {
    if (compile && Component.template && !Component.render) {
      if (__DEV__) {
        startMeasure(instance, `compile`) // 开始执行 compile
      }
      Component.render = compile(Component.template, {/*...*/})
      if (__DEV__) {
        endMeasure(instance, `compile`) // 结束执行 compile
      }
    }
    // ...
    // 这里适配 vue2.0 的旧语法 applyOptions 通过如下顺序初始化:
  // - props (already done outside of this function)
  // - inject
  // - methods
  // - data (deferred since it relies on `this` access)
  // - computed
  // - watch (deferred since it relies on `this` access)
  if (__FEATURE_OPTIONS_API__) {
    currentInstance = instance
    applyOptions(instance, Component) // 适配 vue2.0
    currentInstance = null
  }
//   ...

回到 mountComponent 方法中,init 初始化结束

renderEffect

renderEffect 创建实例 update 方法完毕后 mount 完成

setupRenderEffect(/*...*/)

if (__DEV__) {
  endMeasure(instance, `mount`) // mount 结束
}
// ...

setupRenderEffect:

  • update effect 内的回调函数会在 instance 绑定的响应式数据变化的时候触发
  • update effect 内部判断是首次挂载 mountComponent 还是更新 updateComponent
  • 首先会 render 然后再进行 patch 操作
  const setupRenderEffect: SetupRenderEffectFn = (/*...*/) => {
    instance.update = effect(function componentEffect() {
        // mountComponent
      if (!instance.isMounted) {
        //   重新渲染 render
        if (__DEV__) {
          startMeasure(instance, `render`)
        }
        const subTree = (instance.subTree = renderComponentRoot(instance))
        if (__DEV__) {
          endMeasure(instance, `render`)
        }
        // 渲染完毕后开始 patch
        if (el && hydrateNode) {/*...*/} else {
          if (__DEV__) {
            startMeasure(instance, `patch`)
          }
          patch(
            null, // mountComponent时 为 null
            subTree,
            /*...*/
          )
          if (__DEV__) {
            endMeasure(instance, `patch`)
          }
          // ...
        }
        // ...
      } else {
        // updateComponent
        // ...
        // 同样的需要先 render
        if (__DEV__) {
          startMeasure(instance, `render`)
        }
        const nextTree = renderComponentRoot(instance)
        if (__DEV__) {
          endMeasure(instance, `render`)
        }
        const prevTree = instance.subTree
        // 开始 patch
        if (__DEV__) {
          startMeasure(instance, `patch`)
        }
        patch( // 开始 patch 对比两个节点树
          prevTree,
          nextTree,
          /*...*/
        )
        if (__DEV__) {
          endMeasure(instance, `patch`)
        }
        // ...

最后 mount 结束