源码学习笔记

114 阅读7分钟

vue实例化过程

createApp

  • 调用runtime-dom/index.ts中的createApp方法
  • createApp中调用renderer实例的createApp方法,并将调用参数传递给它
export const createApp = (...args) => {
  return ensureRenderer().createApp(...args);
};
  • 通过ensureRenderer方法调用createRenderer返回以单例模式返回renderer实例
let renderer;
function ensureRenderer() {
  // 如果 renderer有值的话,那么以后都不会初始化了
  return (
    renderer ||
    (renderer = createRenderer({
      createElement,
      createText,
      setText,
      setElementText,
      patchProp,
      insert,
      remove,
    }))
  );
}
  • createRenderer构造函中调用baseCreateRenderer
export function createRenderer<
  HostNode = RendererNode,
  HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
  return baseCreateRenderer<HostNode, HostElement>(options)
}

  • baseCreateRenderer 函数包括了虚拟dom的path、diff等操作,另外创建一个render函数,以闭包的形式调用path方法,并将其传递createAppAPI, 最终返回一个包含creatApp方法的对象
function baseCreateRenderer(
  options: RendererOptions,
  createHydrationFns?: typeof createHydrationFunctions
): any {

  ...
  ... //省略n行
  
  const render: RootRenderFunction = (vnode, container, isSVG) => {
    if (vnode == null) {
      if (container._vnode) {
        unmount(container._vnode, null, null, true)
      }
    } else {
      patch(container._vnode || null, vnode, container, null, null, null, isSVG)
    }
    flushPostFlushCbs()
    container._vnode = vnode
  }
  
  ... 
  ... //省略n行
  
  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  }
}
  • createAppAPI接收两个参数,第一个参数未render函数,返回一个createApp方法,该方法返回vue实例
export function createAppAPI<HostElement>(
  render: RootRenderFunction,
  hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
     return function createApp(rootComponent, rootProps = null) {
          ...
          ... // 省略
          const context = createAppContext()
          const installedPlugins = new Set()
          let isMounted = false
          const app: App = (context.app = {
              _uid: uid++,
              ...
              use(){...},
              mixin(){...},
              component(){...},
              mount(){...},
              unmount(){..},
              ... // 等等方法
          })
          
          return app
     }

}

执行mount挂载过程

mount

  • 调用 app 实例的mount方法,在mount方法中,先调用createVNode方法根据根组件(app.vue)生成虚拟dom结构, 再执行render方法,传入生成虚拟dom,和容器rootContainer (#app)
     ...
     ... // 省略
     mount(
        rootContainer: HostElement,
        isHydrate?: boolean,
        isSVG?: boolean
      ): any {
        debugger
        if (!isMounted) {
          const vnode = createVNode(
            rootComponent as ConcreteComponent,
            rootProps
          )

          ...
          ... // 省略
          if (isHydrate && hydrate) {
            hydrate(vnode as VNode<Node, Element>, rootContainer as any)
          } else {
            render(vnode, rootContainer, isSVG)
          }
          isMounted = true
          app._container = rootContainer
          // for devtools and telemetry
          ;(rootContainer as any).__vue_app__ = app
           ...
           ...
          return getExposeProxy(vnode.component!) || vnode.component!.proxy
        } else if (__DEV__) {
          warn(
            `App has already been mounted.\n` +
              `If you want to remount the same app, move your app creation logic ` +
              `into a factory function and create fresh app instances for each ` +
              `mount - e.g. \`const createMyApp = () => createApp(App)\``
          )
        }
      },
  ... // 省略
  ...

render

  • render方法中调用patch方法,进行dom更新
 // packages\runtime-core\src\renderer.ts 
 // 2332行
  const render: RootRenderFunction = (vnode, container, isSVG) => {
   if (vnode == null) {
     if (container._vnode) {
       unmount(container._vnode, null, null, true)
     }
   } else {
     patch(container._vnode || null, vnode, container, null, null, null, isSVG)
   }
   flushPostFlushCbs()
   container._vnode = vnode
 }

patch

  const patch: PatchFn = (
    n1, // 老节点
    n2, // 新节点
    container,
    anchor = null,
    parentComponent = null,
    parentSuspense = null,
    isSVG = false,
    slotScopeIds = null,
    optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
  ) => {
    // 新老节点一样不做处理
    if (n1 === n2) {
      return
    }
    
    // patching & not same type, unmount old tree
    // 老节点存在,并且新老节点类型不一样时,卸载旧的节点
    // 节点类型一样:n1.type===n2.type&&n1.key===n2.key
    if (n1 && !isSameVNodeType(n1, n2)) {
      anchor = getNextHostNode(n1)
      unmount(n1, parentComponent, parentSuspense, true)
      n1 = null
    }

    if (n2.patchFlag === PatchFlags.BAIL) {
      optimized = false
      n2.dynamicChildren = null
    }

    const { type, ref, shapeFlag } = n2
    // 根据节点类型调用对应的更新方法
    switch (type) {
      // 文本节点
      case Text:
        processText(n1, n2, container, anchor)
        break
      // 注释节点
      case Comment:
        processCommentNode(n1, n2, container, anchor)
        break
      // 静态节点(无状态节点)
      case Static:
        if (n1 == null) {
          mountStaticNode(n2, container, anchor, isSVG)
        } else if (__DEV__) {
          patchStaticNode(n1, n2, container, isSVG)
        }
        break
      // 文档碎片
      case Fragment:
        processFragment(
          n1,
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized
        )
        break
      default:
        // html节点 | type为string类型
        if (shapeFlag & ShapeFlags.ELEMENT) {
          processElement(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
          // 组件节点(函数组件|有状态组件)| type 为对象或者函数
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
          processComponent(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
          // 使用指令移动得节点(比如弹框,会移动到body下)
        } else if (shapeFlag & ShapeFlags.TELEPORT) {
          ;(type as typeof TeleportImpl).process(
            n1 as TeleportVNode,
            n2 as TeleportVNode,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized,
            internals
          )
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
          ;(type as typeof SuspenseImpl).process(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized,
            internals
          )
        } else if (__DEV__) {
          warn('Invalid VNode type:', type, `(${typeof type})`)
        }
    }

  }
 ....
 .... // 省略
  • 调用mount方法穿入的跟组件app.vue是一个对象,所以调用processComponent方法
  • processComponent 方法中根据老节点(n1)是否存在,判断是要执行更新还是创建操作,如果组件没有被缓存,调用mountComponent挂载组件
   const processComponent = (
    n1: VNode | null,
    n2: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean
  ) => {
    n2.slotScopeIds = slotScopeIds
    // 创建
    if (n1 == null) {
      // 使用了keep_alive
      if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
        ;(parentComponent!.ctx as KeepAliveContext).activate(
          n2,
          container,
          anchor,
          isSVG,
          optimized
        )
      } else {
        mountComponent(
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
      }
    } else {
      // 更新
      updateComponent(n1, n2, optimized)
    }
  }
  • mountComponent中调用先调用createComponentInstance创建组件实例,然后调用setupComponent方法确初始化组件的propsslots->initProps initSlots,并且确定render函数
    1. 首先处理setup,如果setup存在并且返回了一个function,则用它作为组件的render函数,如果返回了一个对象,并且是虚拟dom,或者返回了一个promise并且父组件不是Suspense则抛出警告

    2. setup不存在,或者返回的不是一个function时,则使用type(组件)的render作为组件实例(instance)的render函数

    3. 处理setup时会根据setup参数个数决定是否调用createSetupContext创建包含attrsslotsemitexpose属性的对象,即setp上下文, 参数个数时才会创建

    渲染函数render的优先级:setup中return的function>组件中render函数选项>template

  const mountComponent: MountComponentFn = (
   initialVNode,
   container,
   anchor,
   parentComponent,
   parentSuspense,
   isSVG,
   optimized
 ) => {
   // 2.x compat may pre-create the component instance before actually
   // mounting
   const compatMountInstance =
     __COMPAT__ && initialVNode.isCompatRoot && initialVNode.component
   // 创建组件实例
   const instance: ComponentInternalInstance =
     compatMountInstance ||
     (initialVNode.component = createComponentInstance(
       initialVNode, // 虚拟dom
       parentComponent, // 父组件
       parentSuspense
     ))
     ....
     .... // 省略
   // resolve props and slots for setup context
   if (!(__COMPAT__ && compatMountInstance)) {
     if (__DEV__) {
       startMeasure(instance, `init`)
     }
     /**
      * 1. 初始化props/slots
      * 2. 如果是有状态组件,执行setupStatefulComponent
      *  - 如果是开发环境 校验组件命名|校验指令命名是否合法
      *  - 处理setup, 如果setup得参数个数大于1则创setup建执行上下文(context包括:attrs、emits、slots、expose)
      * 2.给instance实例添加render方法
      * 3.触发beforeCreate/created钩子(兼容2.x)
      */ 
     setupComponent(instance)
     ....
     .... // 省略不重要的东西
   }

   // setup() is async. This component relies on async logic to be resolved
   // before proceeding
   // setup返回了一个promise时,要做异步逻辑解析处理
   if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
     parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect)

     // Give it a placeholder if this is not hydration
     // TODO handle self-defined fallback
     // 有异步依赖的组件, el不存在,先创建一个注释占位
     if (!initialVNode.el) {
       const placeholder = (instance.subTree = createVNode(Comment))
       processCommentNode(null, placeholder, container!, anchor)
     }
     return
   }
   // 开始组件渲染
   setupRenderEffect(
     instance,
     initialVNode,
     container,
     anchor,
     parentSuspense,
     isSVG,
     optimized
   )
   .... // 省略无用
   ....
 }
  • setupComponent,对组件进行初始化,并调用beforeCreate/beforeCreated钩子
  1. 先初始化props/slots, 如果是有状态组件调用setupStatefulComponent,setup存在, 创建setup执行上下文,并拿到setup函数返回值,不存在直接调用finishComponentSetup

  2. 调用handleSetupResult处理返回值,如果返回一个函数,则将其作为render函数,如果是虚拟dom对象或者undefined则抛出警告

  3. 最后调用finishComponentSetup,如果组件的没有render函数,则编辑template模板,作为render函数

  4. 然后带调用applyOptions处理组件选项

  5. 合并mixins和全局(跟组件)属性方法->触发beforeCreate->初始化methods,用bind改变this指向/初始化data/computed/watcher/provide->调用created钩子

        
    
  • setupRenderEffect
    1. 创建组件更新函数componentUpdateFn,
    const setupRenderEffect: SetupRenderEffectFn = (
    instance,
    initialVNode,
    container,
    anchor,
    parentSuspense,
    isSVG,
    optimized
  ) => {
    // 组件更新函数
    const componentUpdateFn = () => {
      if (!instance.isMounted) { // 组件未挂载
        ....
        ....
      } else {
        ....
        ....
      }
    }
    // create reactive effect for rendering
    const effect = (instance.effect = new ReactiveEffect(
      componentUpdateFn,
      () => queueJob(instance.update),
      instance.scope // track it in component's effect scope
    ))

    const update = (instance.update = effect.run.bind(effect) as SchedulerJob)
    update.id = instance.uid
    ....
    ....// 省略
    update()
  }
  • componentUpdateFn 组件更新函数
const componentUpdateFn = () => {
      if (!instance.isMounted) { // 组件未挂载
        let vnodeHook: VNodeHook | null | undefined
        ...
        ...
        // beforeMount hook
        // 执行beforeMount钩子,bm为一个数组,钩子函数可以有多个
        if (bm) {
          invokeArrayFns(bm为一个数组,钩子函数可以有多个)
        }
        // onVnodeBeforeMount
        // 执行beforeMount指令
        if (
          !isAsyncWrapperVNode &&
          (vnodeHook = props && props.onVnodeBeforeMount)
        ) {
          invokeVNodeHook(vnodeHook, parent, initialVNode)
        }
        // 触发 hook:beforeMount
        if (
          __COMPAT__ &&
          isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
        ) {
          instance.emit('hook:beforeMount')
        }
        ....
        .... // 省略
        // 执行instance 的 render,渲染组件(el的子组件)
        const subTree = (instance.subTree = renderComponentRoot(instance))
        ....
        ....
        // 递归调用patch方法,以此进行所有子节点(组件)dom创建/更新
        patch(
           null,
           subTree,
           container,
           anchor,
           instance,
           parentSuspense,
           isSVG
         )

        // mounted hook
        // 组件挂载完成,执行mounted钩子
        if (m) {
          queuePostRenderEffect(m, parentSuspense)
        }
        // onVnodeMounted
        if (
          !isAsyncWrapperVNode &&
          (vnodeHook = props && props.onVnodeMounted)
        ) {
          const scopedInitialVNode = initialVNode
          queuePostRenderEffect(
            () => invokeVNodeHook(vnodeHook!, parent, scopedInitialVNode),
            parentSuspense
          )
        }
        if (
          __COMPAT__ &&
          isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
        ) {
          queuePostRenderEffect(
            () => instance.emit('hook:mounted'),
            parentSuspense
          )
        }

        // activated hook for keep-alive roots.
        // #1742 activated hook must be accessed after first render
        // since the hook may be injected by a child keep-alive
        
        // 使用了keep-alive,触发activated钩子
        if (initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
          instance.a && queuePostRenderEffect(instance.a, parentSuspense)
          if (
            __COMPAT__ &&
            isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
          ) {
            queuePostRenderEffect(
              () => instance.emit('hook:activated'),
              parentSuspense
            )
          }
        }
        instance.isMounted = true

        if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
          devtoolsComponentAdded(instance)
        }

        // #2458: deference mount-only object parameters to prevent memleaks
        initialVNode = container = anchor = null as any
      } else {
        // updateComponent
        // This is triggered by mutation of component's own state (next: null)
        // OR parent calling processComponent (next: VNode)
        let { next, bu, u, parent, vnode } = instance
        let originNext = next
        let vnodeHook: VNodeHook | null | undefined
        if (__DEV__) {
          pushWarningContext(next || instance.vnode)
        }

        // Disallow component effect recursion during pre-lifecycle hooks.
        toggleRecurse(instance, false)
        if (next) {
          next.el = vnode.el
          updateComponentPreRender(instance, next, optimized)
        } else {
          next = vnode
        }

        // beforeUpdate hook
        // 触发beforeUpdate钩子
        if (bu) {
          invokeArrayFns(bu)
        }
        // onVnodeBeforeUpdate
        if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
          invokeVNodeHook(vnodeHook, parent, next, vnode)
        }
        if (
          __COMPAT__ &&
          isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
        ) {
          instance.emit('hook:beforeUpdate')
        }
        ....
        ....// 省略
        // 更新子节点
        patch(
          prevTree,
          nextTree,
          // parent may have changed if it's in a teleport
          hostParentNode(prevTree.el!)!,
          // anchor may have changed if it's in a fragment
          getNextHostNode(prevTree),
          instance,
          parentSuspense,
          isSVG
        )
        ....
        ....
        // updated hook
        // 触发updated钩子
        if (u) {
          queuePostRenderEffect(u, parentSuspense)
        }
        // onVnodeUpdated
        if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
          queuePostRenderEffect(
            () => invokeVNodeHook(vnodeHook!, parent, next!, vnode),
            parentSuspense
          )
        }
        if (
          __COMPAT__ &&
          isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
        ) {
          queuePostRenderEffect(
            () => instance.emit('hook:updated'),
            parentSuspense
          )
        }
        ....
      }
    }

实例化并挂载过程总结

graph TD
createApp --> 返回Vue实例 --> 执行mount --> 执行render方法 --> patch --> 
           判断节点类型type/ShapeFlags --> 节点类型为函数或者有状态组件 --> processComponent --> mountComponent:调用createComponentInstance创建组件实例 --> setupComponent设置/校验组件状态,执行setup,确定组件的render函数,并触发beforeCreate钩子--> 对组件data/method/computed等属性做代理或添加响应式等操作--> 触发created钩子,并注册之后得声明周期钩子 --> setupRenderEffect --> componentUpdateFn:组件更新函数,先触发beforeMount --> 调用组件实例render方法获取子节点subTree,调用patch方法,然后触发mounted钩子,再触发activited钩子 --> patch
判断节点类型type/ShapeFlags --> 节点类型为element,即type为string --> 根据type创建el/或者更新 --> 如果子节点为数组--> mountChildren:遍历调用patch --> patch


shapeFlag(节点类型判断)

  const shapeFlag = isString(type)
    ? ShapeFlags.ELEMENT
    : __FEATURE_SUSPENSE__ && isSuspense(type)
    ? ShapeFlags.SUSPENSE
    : isTeleport(type)
    ? ShapeFlags.TELEPORT
    : isObject(type)
    ? ShapeFlags.STATEFUL_COMPONENT
    : isFunction(type)
    ? ShapeFlags.FUNCTIONAL_COMPONENT
    : 0