该文章已不在维护,请移步
createApp

挂载方法

vue3的挂载方式和vue2不同,vue3是这样的:

  1. Vue.createApp(options).mount('#demo')

流程图

CreateApp 流程图如下:

createApp

废话不多说,我们来直接看源代码:

function ensureRenderer() {
  return renderer || (renderer = createRenderer(rendererOptions));
}

const createApp = ((...args) => {
  const app = ensureRenderer().createApp(...args); // 创建app实例
  const { mount } = app;   // 重写mount方法,可以独立到不同的平台实现
  app.mount = (containerOrSelector) => {
    const container = normalizeContainer(containerOrSelector);
    if (!container)
      return;

    const component = app._component
    // 不是函数组件 && 没有自定义render && 没有自定义template
    if (!isFunction(component) && !component.render && !component.template) {
      // 将容器的html赋值给模版
      component.template = container.innerHTML
    }

    // 安装前清除内容
    container.innerHTML = '';
    const proxy = mount(container);  // DOM容器处理,执行createAppAPI.mount
    if (container instanceof Element) {
      container.removeAttribute('v-cloak');
      container.setAttribute('data-v-app', '');
    }
    return proxy;
  };
  return app;
});

createRenderer

patch

这里 createRenderer 是关键,我们继续看:

function createRenderer(options) {
  return baseCreateRenderer(options);
}

function baseCreateRenderer(options, createHydrationFns) {
  const { insert: hostInsert, remove: hostRemove, patchProp: hostPatchProp, forcePatchProp: hostForcePatchProp, createElement: hostCreateElement, createText: hostCreateText, createComment: hostCreateComment, setText: hostSetText, setElementText: hostSetElementText, parentNode: hostParentNode, nextSibling: hostNextSibling, setScopeId: hostSetScopeId = NOOP, cloneNode: hostCloneNode, insertStaticContent: hostInsertStaticContent } = options;
  /**
      patch: 补丁
    是vnode对dom的一种抽象表达,是将vnode的信息映射到dom上;
       patch方法,接受新旧VNode节点与对应的容器,节点的兄弟节点,父组件,是否压缩优化等等,
       整个patch的过程其实就是对应的VNode深度优先遍历的过程;最终构造出一颗完整的树
  */
  const patch = (
    n1,  // 旧vnode
    n2,  // 新vnode
    container, 
    anchor = null, // 定位锚点dom,用于往锚点前插节点
    parentComponent = null, 
    parentSuspense = null, 
    isSVG = false, 
    optimized = false // 是否启用diff优化
  ) => {
    // 存在旧的VNode,并且新的VNode类型(key|type)与之不同,销毁对应的旧结点
    if (n1 && !isSameVNodeType(n1, n2)) {
      anchor = getNextHostNode(n1);
      //n1设置为null,保证后面走整个节点的mount逻辑
      n1 = null;
    }
    if (n2.patchFlag === -2 /* BAIL */) {
      optimized = false;
      n2.dynamicChildren = null;
    }
      /**
        patch内部有两种情况:
        1. 挂载 n1 为null
        2. 更新
      */
      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 {
            patchStaticNode(n1, n2, container, isSVG);
          }
          break;
        case Fragment: //处理Fragment元素</>
          processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
          break;
        default:  //elemment 处理DOM元素
          if (shapeFlag & 1 /* ELEMENT */) {
            // 处理element
            processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
          }
          else if (shapeFlag & 6 /* COMPONENT */) {
            // 处理组件
            processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
          }
          else if (shapeFlag & 64 /* TELEPORT */) {
            // 处理teleport
            type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals);
          }
          else if ( shapeFlag & 128 /* SUSPENSE */) {
            // 处理 suspense
            type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals);
          }
          else {
            warn('Invalid VNode type:', type, `(${typeof type})`);
          }
      }
      // set ref
      if (ref != null && parentComponent) {
        setRef(ref, n1 && n1.ref, parentSuspense, n2);
      }
    }

    const processText = () => {...}; // 处理文本
    const processCommentNode = () => {...}  //处理注释
    const mountStaticNode = () => {...}  // 挂载静态节点
    const patchStaticNode = () => {...}  // 更新静态节点
    const processElement = () => {...}   // 处理element
    const mountElement = () => {...}     // 挂载element
    const mountChildren = () => {...}    // 挂载子元素,跟深度遍历上面的patch方法
    const patchElement = () => {...}     // 更新element节点,主要是更新props和子节点,内部还涉及到动态patch更新t
    const patchProps = () => {...}       // 处理元素的props属性
    const processFragment = () =>  {...} // 处理Fragment
    const processComponent = () => {...} // 处理组件,内部会调用 mountComponent
    const mountComponent = () => {...}   // 挂载组件
    const updateComponent = () => {...}  // 更新组件
    const setupRenderEffect = () => {...} // 创建一个响应式的渲染函数,赋值给app实例
    const updateComponentPreRender = () => {...} // 更新组件信息
    const patchChildren = () => {...}   // 更新子节点
    const patchKeyedChildren = () => {...} // diff,数组子节点发生变更,主要是,更新、删除、添加、移动几种方式处理
    const move = () => {...}  // 移动或插入子节点
    const unmount = () => {...}  // 卸载
    const remove = () => {...}   // 删除元素或vnode
    const removeFragment = () => {...}   // 删除Fragment
    const unmountComponent = () => {...} // 卸载所有子组件
    const unmountChildren = () => {...} // 卸载所有子节点
    const getNextHostNode = () => {...}  // 获取下一个兄弟节点

    const render = (vnode, container) => {
      if (vnode == null) {
        // 没有vnode,卸载
        if (container._vnode) {
          unmount(container._vnode, null, null, true);
        }
      }
      else {
        // 创建或更新组件
        patch(container._vnode || null, vnode, container);
      }
      // 队列检测,如果有值就刷新(vue的调度系统机制,类似于Fiber)
      flushPostFlushCbs();
      // 缓存vnode
      container._vnode = vnode;
    };

    return {
      render,
      hydrate, 
      createApp: createAppAPI(render, hydrate)
    };
}

看到这,一脸嫌弃,这么多的处理。。。
我们来看几个重点的方法吧。

processText

处理文本:

const processText = (n1, n2, container, anchor) => {
  if (n1 == null) {
    // 旧节点不存在,创建一个新的文本节点
    hostInsert((n2.el = hostCreateText(n2.children)), container, anchor)
  } else {
    const el = (n2.el = n1.el)
    if (n2.children !== n1.children) {
      // 两个内容不相同,覆盖文本,n2.children 就是字符串文本。
      hostSetText(el, n2.children)
    }
  }
}

processElement

处理element,首次挂载,其他就是更新:

const processElement = ( n1, n2, container, ... ) => {
  isSVG = isSVG || n2.type === 'svg'
  if (n1 == null) {
    // 初始化挂载节点element
    mountElement( n2,  container, ...)
  } else {
    patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized)
  }
}

mountElement

挂载element:

const mountElement = (
      vnode,
      container,
      anchor,
      parentComponent,
      parentSuspense,
      isSVG,
      optimized
    ) => {
      let el
      let vnodeHook
      const {
        type,
        props,
        shapeFlag,
        transition,
        scopeId,
        patchFlag,
        dirs,
      } = vnode
      {
        // 创建dom节点
        el = vnode.el = hostCreateElement(vnode.type, isSVG, props && props.is)
        // 处理文本节点
        if (shapeFlag & 8 /* TEXT_CHILDREN */) {
          hostSetElementText(el, vnode.children)
        } else if (shapeFlag & 16 /* ARRAY_CHILDREN */) {
          // 处理数组子节点
          mountChildren(
            vnode.children,
            el,
            null,
            parentComponent,
            parentSuspense,
            isSVG && type !== 'foreignObject',
            optimized || !!vnode.dynamicChildren
          )
        }
        // 调用指令hook的生命周期方法:created
        if (dirs) {
          invokeDirectiveHook(vnode, null, parentComponent, 'created')
        }
        // vnode的props:例如 { id: 'search', type:'button' }
        if (props) {
          for (const key in props) {
            if (!isReservedProp(key)) {
              hostPatchProp(el, key, null,props[key], ...)
            }
          }
        }
        //设置dom的一些attr属性
        setScopeId(el, scopeId, vnode, parentComponent)
      }
          // 将虚拟节点缓存给dom,且不能被枚举出来。
      {
        Object.defineProperty(el, '__vnode', {
          value: vnode,
          enumerable: false,
        })
        Object.defineProperty(el, '__vueParentComponent', {
          value: parentComponent,
          enumerable: false,
        })
      }
      if (dirs) {
        // 调用指令hook的生命周期方法: beforeMount
        invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount')
      }
      // 把创建的 element,插入到节点中
      hostInsert(el, container, anchor)
      if (
        (vnodeHook = props && props.onVnodeMounted) ||
        needCallTransitionHooks ||
        dirs
      ) {
        queuePostRenderEffect(() => {
          // 挂载组件的effect队列,数据发生改变会执行该函数
          vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
          needCallTransitionHooks && transition.enter(el)
          dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
        }, parentSuspense)
      }
    }

整个函数做这几件事情:

  1. 创建 DOM 元素节点(hostCreateElement)
  2. 处理 props
  3. 处理 children 元素(此方式与组件处理类似,递归调用 patch 流程)
  4. 挂载 DOM 元素到 container 上
  5. 调用了钩子的一些生命周期方法
  6. 对 transition 的单独处理

patchElement

更新element节点。

const patchElement = (
      n1,
      n2,
      parentComponent,
      parentSuspense,
      isSVG,
      optimized
    ) => {
      const el = (n2.el = n1.el)
      let { patchFlag, dynamicChildren, dirs } = n2

      if (patchFlag > 0) {
         /**
         * patchFlag的存在意味着该元素的呈现代码
         * 在这个路径中,旧节点和新节点的形状是相同的
         */
        if (patchFlag & 16 /* FULL_PROPS */) {
          // 更新动态属性key
          patchProps(
            el,
            n2,
            oldProps,
            newProps,
            parentComponent,
            parentSuspense,
            isSVG
          )
        } else {
          if (patchFlag & 2 /* CLASS */) {
            if (oldProps.class !== newProps.class) {
              // 更新class
              hostPatchProp(el, 'class', null, newProps.class, isSVG)
            }
          }
          if (patchFlag & 4 /* STYLE */) {
            // 更新 style
            hostPatchProp(el, 'style', oldProps.style, newProps.style, isSVG)
          }
          if (patchFlag & 8 /* PROPS */) {
            const propsToUpdate = n2.dynamicProps
            /**
             * propsToUpdate是 onClick | onUpdate:modelValue 这些。
             * 示例:<polygon :points="points"></polygon>  propsToUpdate === ["points"]
             */
            for (let i = 0; i < propsToUpdate.length; i++) {
              const key = propsToUpdate[i]
              const prev = oldProps[key]
              const next = newProps[key]
              // 这里的next可能是 string number function object boolean
              // 比较不同就更新
              if (
                next !== prev ||
                (hostForcePatchProp && hostForcePatchProp(el, key))
              ) {
                hostPatchProp(el, key,prev,next, ...)    
              }
            }
          }
        }
        // 文本更新
        if (patchFlag & 1 /* TEXT */) {
          if (n1.children !== n2.children) {
            hostSetElementText(el, n2.children)
          }
        }
      } 
    }

patchChildren

更新子节点:

const patchChildren = (
      n1,
      n2,
      container,
      anchor,
      parentComponent,
      parentSuspense,
      isSVG,
      optimized = false
    ) => {
      const c1 = n1 && n1.children
      const prevShapeFlag = n1 ? n1.shapeFlag : 0 // 旧节点
      const c2 = n2.children
      const { patchFlag, shapeFlag } = n2 // 新节点
      // fast path
      if (patchFlag > 0) {
        if (patchFlag & 128 /* KEYED_FRAGMENT */) {
          patchKeyedChildren(
            c1,
            c2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            optimized
          )
          return
        } else if (patchFlag & 256 /* UNKEYED_FRAGMENT */) {
          // unkeyed
          patchUnkeyedChildren(
            c1,
            c2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            optimized
          )
          return
        }
      }
      // children有3种可能:文本、数组或没有children。
      if (shapeFlag & 8 /* TEXT_CHILDREN */) {
        if (prevShapeFlag & 16 /* ARRAY_CHILDREN */) {
          // 新节点是文本,旧节点是数组,直接删除。
          unmountChildren(c1, parentComponent, parentSuspense)
        }
        // 新节点是文本,和旧节点不相同,直接替换。
        if (c2 !== c1) {
          hostSetElementText(container, c2)
        }
      } else {
        if (prevShapeFlag & 16 /* ARRAY_CHILDREN */) {
          if (shapeFlag & 16 /* ARRAY_CHILDREN */) {
            // 新节点和旧节点都是数组,diff比较
            patchKeyedChildren(
              c1,
              c2,
              container,
              anchor,
              parentComponent,
              parentSuspense,
              isSVG,
              optimized
            )
          } else {
            // 新节点不是数组,旧节点是数组,删除子节点
            unmountChildren(c1, parentComponent, parentSuspense, true)
          }
        } else {
          // 子节点是文本或null
          // 新节点是数组或null
          if (prevShapeFlag & 8 /* TEXT_CHILDREN */) {
            // 替换空文本
            hostSetElementText(container, '')
          }
          // 挂载一个新的数组子节点
          if (shapeFlag & 16 /* ARRAY_CHILDREN */) {
            mountChildren(
              c2,
              container,
              anchor,
              parentComponent,
              parentSuspense,
              isSVG,
              optimized
            )
          }
        }
      }
    }

总结

  1. 新节点是文本
    • 1 - 旧节点是数组,删除所有字节点,插入文本
    • 2 - 旧节点不是是文本,替换文本
    • 3 - 旧节点是空,添加文本
  2. 新节点是数组
    • 1 - 旧节点是数组,diff比较子节点
    • 2 - 旧节点不是数组,删除所有子节点,挂载新的子节点
    • 3 - 旧节点是空,挂载新的子节点
  3. 新节点是空
    • 1 - 直接删除

processFragment

处理 Fragment

const processFragment = (
      n1,
      n2,
      container,
      anchor,
      parentComponent,
      parentSuspense,
      isSVG,
      optimized
    ) => {
        // 创建两个空节点
      const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(''))
      const fragmentEndAnchor = (n2.anchor = n1
        ? n1.anchor
        : hostCreateText(''))
      let { patchFlag, dynamicChildren } = n2
      if (patchFlag > 0) {
        optimized = true
      }

      if (n1 == null) {
        // 初始化挂载
        hostInsert(fragmentStartAnchor, container, anchor)
        hostInsert(fragmentEndAnchor, container, anchor)
        // 挂载子节点,这里只能是数组的子集
        mountChildren(
          n2.children,
          container,
          fragmentEndAnchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
      } else {
        // 更新Fragment
        if (
          patchFlag > 0 &&
          patchFlag & 64 /* STABLE_FRAGMENT */ &&
          dynamicChildren &&
          n1.dynamicChildren
        ) {
          // 更新整个block,内部会遍历patch
          patchBlockChildren(
            n1.dynamicChildren,
            dynamicChildren,
            container,
            parentComponent,
            parentSuspense,
            isSVG
          )
        } else {
          // 更新子节点,遍历patch
          patchChildren(
            n1,
            n2,
            container,
            fragmentEndAnchor,
            parentComponent,
            parentSuspense,
            isSVG,
            optimized
          )
        }
      }
    }

createComponentInstance

创建组件实例。

function createComponentInstance(vnode, parent, suspense) {
  const type = vnode.type
  // inherit parent app context - or - if root, adopt from root vnode
  // 从父组件实例继承应用上下文,如果没有父组件,说明是根组件实例,
  // 直接从根级vnode中获取应用上下文
  const appContext =
        (parent ? parent.appContext : vnode.appContext) || emptyAppContext
  const instance = {
    uid: uid$2++,
    vnode,  // 当前组件vnode
    type,
    parent,  // 父组件实例
    appContext,
    root: null, // 根组件实例
    next: null,
    subTree: null,   // 组件的渲染vnode,由组件的render函数生成
    update: null,    // 组件内容挂载 / 更新到视图的执行回调
    render: null,    // 组件的render函数
    proxy: null,
    exposed: null,
    withProxy: null,
    effects: null,    // 组件相关的依赖集合
    provides: parent ? parent.provides : Object.create(appContext.provides),
    accessCache: null,
    renderCache: [],
    // local resovled assets
    components: null,   // 注册在当前组件下组件options
    directives: null,   // 注册在当前组件下的指令
    // resolved props and emits options
    propsOptions: normalizePropsOptions(type, appContext),
    emitsOptions: normalizeEmitsOptions(type, appContext),
    // emit
    emit: null,
    emitted: null,
    // state
    ctx: EMPTY_OBJ,
    data: EMPTY_OBJ,
    props: EMPTY_OBJ,
    attrs: EMPTY_OBJ,
    slots: EMPTY_OBJ,
    refs: EMPTY_OBJ,
    setupState: EMPTY_OBJ,   // 用于挂载setup执行的结果
    setupContext: null,
    // suspense related
    suspense,
    suspenseId: suspense ? suspense.pendingId : 0,
    asyncDep: null,
    asyncResolved: false,
    // lifecycle hooks
    // not using enums here because it results in computed properties
    // 生命周期钩子及相应的标志位
    isMounted: false,
    isUnmounted: false,
    isDeactivated: false,
    bc: null,
    c: null,
    bm: null,
    m: null,
    bu: null,
    u: null,
    um: null,
    bum: null,
    da: null,
    a: null,
    rtg: null,
    rtc: null,
    ec: null,
  }
  {
    instance.ctx = createRenderContext(instance)
  }
  instance.root = parent ? parent.root : instance
  instance.emit = emit.bind(null, instance)
  {
    devtoolsComponentAdded(instance)
  }
  return instance
}

setupComponent

组件初始化。
这里的setup就是我们在组件里面的setup函数。

function setupComponent(instance, isSSR = false) {
  isInSSRComponentSetup = isSSR
  const { props, children, shapeFlag } = instance.vnode
  // 是否是状态型组件
  const isStateful = shapeFlag & 4 /* STATEFUL_COMPONENT */
  // 初始化组件属性、插槽
  initProps(instance, props, isStateful, isSSR)
  initSlots(instance, children)
  // 仅为状态型组件挂载setup信息,非状态型组件仅为纯UI展示不需要挂载状态信息
  const setupResult = isStateful
  ? setupStatefulComponent(instance, isSSR)
  : undefined
  isInSSRComponentSetup = false
  return setupResult
}

 // 初始化组件props
  function initProps(
    instance,
    rawProps,
    isStateful, // 位标志比较结果
    isSSR = false
  ) {
    const props = {}
    const attrs = {}
    // 定义内部属性并通过defineProperty设置属性不可枚举
    def(attrs, InternalObjectKey, 1)
     // 初始化外部定义属性
    setFullProps(instance, rawProps, props, attrs)
    // 校验prps
    validateProps(props, instance)
    if (isStateful) {
      // 状态型组件将处理后的属性挂载到组件实例上
      // 属性做浅响应式转化
      instance.props = isSSR ? props : shallowReactive(props)
    } else {
      if (!instance.type.props) {
        instance.props = attrs
      } else {
        instance.props = props
      }
    }
    instance.attrs = attrs
  }

  // 状态型组件生成setup结果并进行信息挂载
  function setupStatefulComponent(instance, isSSR) {
    // 组件options Object,也就是vnode.type
    const Component = instance.type
    {
      if (Component.name) {
        // 校验组件名是否合法
        validateComponentName(Component.name, instance.appContext.config)
      }
      if (Component.components) {
        // 批量校验组件名
        const names = Object.keys(Component.components)
        for (let i = 0; i < names.length; i++) {
          validateComponentName(names[i], instance.appContext.config)
        }
      }
      if (Component.directives) {
        // 批量校验指令名是否合法
        const names = Object.keys(Component.directives)
        for (let i = 0; i < names.length; i++) {
          validateDirectiveName(names[i])
        }
      }
    }
    instance.accessCache = Object.create(null)
    // 为组件实例创建渲染代理
    instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
    {
      exposePropsOnRenderContext(instance)
    }
    const { setup } = Component
    if (setup) {
      // 创建setup上下文并挂载到组件实例上
      const setupContext = (instance.setupContext =
        setup.length > 1 ? createSetupContext(instance) : null)
      // 记录当前正在初始化的组件
      currentInstance = instance
      // 执行setup前暂停依赖收集,以免产生不可预测的问题
      pauseTracking()
      // 执行setup并获得安装结果信息,setup执行结果就是我们定义的响应式数据、函数、钩子等
      const setupResult = callWithErrorHandling(
        setup,
        instance,
        0 /* SETUP_FUNCTION */,
        [shallowReadonly(instance.props), setupContext]
      )
      // setup执行完毕恢复依赖收集
      resetTracking()
      // 当前实例重置
      currentInstance = null

      // setup执行结果
      if (isPromise(setupResult)) {
        if (isSSR) {
          return setupResult.then((resolvedResult) => {
            handleSetupResult(instance, resolvedResult)
          })
        } else {
          instance.asyncDep = setupResult
        }
      } else {
        // 将setup的执行结果挂载到组件实例上
        handleSetupResult(instance, setupResult)
      }
    } else {
      finishComponentSetup(instance)
    }
  }

  // 处理setup执行结果,将结果挂载到组件实例,setup执行结果可以是内联render函数、Object
  function handleSetupResult(instance, setupResult, isSSR) {
    if (isFunction(setupResult)) {
      {
        instance.render = setupResult
      }
    } else if (isObject(setupResult)) {
      if (isVNode(setupResult)) {
        warn(
          `setup() should not return VNodes directly - ` +
            `return a render function instead.`
        )
      }
      {
        instance.devtoolsRawSetupState = setupResult
      }
      // 为setup结果做代理拦截,并将代理结果挂载到组件的setupState上
      instance.setupState = proxyRefs(setupResult)
      {
        exposeSetupStateOnRenderContext(instance)
      }
    } else if (setupResult !== undefined) {
      warn(
        `setup() should return an object. Received: ${
          setupResult === null ? 'null' : typeof setupResult
        }`
      )
    }
    finishComponentSetup(instance)
  }

mountComponent

挂载组件。

const mountComponent = (initialVNode, container, anchor, ...) => {
  // 1.创建组件实例,见上面的方法
  const instance = (initialVNode.component = createComponentInstance(
    initialVNode,
    parentComponent,
    parentSuspense
  ))

  // 2.设置组件实例,见上面的方法
  setupComponent(instance)

  // 3.设置并且运行带有副作用的渲染函数,见下面的方法
  setupRenderEffect(instance, ...) 
}

该方法做了3件重要的事情:

  • 创建组件实例
  • 设置组件实例,挂载setup,包括里面的钩子
  • 生成渲染的effect

setupRenderEffect

运行带有副作用的render。

const setupRenderEffect = (
  instance,
  initialVNode,
  container,
  anchor,
  parentSuspense,
  isSVG,
  optimized
) => {
  /* 
  创建一个响应式的渲染函数,当组件内容有数据变化时,会执行此方法。
  这里会立即执行一次
  */
  instance.update = effect(function componentEffect() {
    // 首次挂载,初始化渲染
    if (!instance.isMounted) {
      let vnodeHook
      const { el, props } = initialVNode
      const { bm, m, parent } = instance
      // console.log('props', props)
      // beforeMount hook  执行beforeMount(挂载之前)钩子函数,是个数组。
      if (bm) {
        invokeArrayFns(bm)
      }
      // onVnodeBeforeMount
      if ((vnodeHook = props && props.onVnodeBeforeMount)) {
        invokeVNodeHook(vnodeHook, parent, initialVNode)
      }
      // render 开始性能监控
      startMeasure(instance, `render`)

      // 生成子树结构
      const subTree = (instance.subTree = renderComponentRoot(instance))
      endMeasure(instance, `render`)

      startMeasure(instance, `patch`)
      // 将子树渲染到container中
      patch(
        null,
        subTree,
        container,
        anchor,
        instance,
        parentSuspense,
        isSVG
      )
      endMeasure(instance, `patch`)

      initialVNode.el = subTree.el
      // 执行mount(挂载之后)钩子函数
      if (m) {
        queuePostRenderEffect(m, parentSuspense)
      }

      //激活钩子保持根系活力。
      //#1742 首次渲染后必须访问激活的钩子
      //因为钩子可能会被children注入,所以要保持生命周期
      const { a } = instance
      if (
        a &&
        initialVNode.shapeFlag & 256 /* COMPONENT_SHOULD_KEEP_ALIVE */
      ) {
        queuePostRenderEffect(a, parentSuspense)
      }
      instance.isMounted = true

      // 清空
      initialVNode = container = anchor = null
    } else {
      // updateComponent 更新组件,非首次
      let { next, bu, u, parent, vnode } = instance
      let originNext = next
      let vnodeHook
      pushWarningContext(next || instance.vnode)
      if (next) {
        next.el = vnode.el
        updateComponentPreRender(instance, next, optimized)
      } else {
        next = vnode
      }

      // beforeUpdate hook
      if (bu) {
        invokeArrayFns(bu)
      }

      // render 性能监控
      startMeasure(instance, `render`)
      const nextTree = renderComponentRoot(instance)
      endMeasure(instance, `render`)
      const prevTree = instance.subTree
      instance.subTree = nextTree
      startMeasure(instance, `patch`)

      // 把对应的subTree渲染到container中,子树可能是element、text、component等
      patch(
        prevTree,
        nextTree,
        // 如果在传送中,父级可能已经改变了
        hostParentNode(prevTree.el),
        // 如果在碎片中,它可能已经改变了
        getNextHostNode(prevTree),
        instance,
        parentSuspense,
        isSVG
      )
      endMeasure(instance, `patch`)

      next.el = nextTree.el

      // updated 钩子
      if (u) {
        queuePostRenderEffect(u, parentSuspense)
      }
    }
  }, createDevEffectOptions(instance)) // 创建批量更新方法
}

// 这里的scheduler,就是Trigger派发时执行的 effect.options.scheduler
// 具体怎么派发的,我们下章讲。
function createDevEffectOptions(instance) {
  return {
    scheduler: queueJob,
    allowRecurse: true,
    onTrack: instance.rtc ? (e) => invokeArrayFns(instance.rtc, e) : void 0,
    onTrigger: instance.rtg ? (e) => invokeArrayFns(instance.rtg, e) : void 0,
  }
}

该方法的功能解析:

  • 首页挂载,初始化
    • 执行 beforeMount 钩子
    • render 性能监控
    • 生成对应的 subTree
    • 执行 patch 将 subTree 渲染到 container
    • 执行 mount 钩子
    • 执行 keep-alive 的 activated 钩子
  • 非首次,更新
    • 更新组件信息
    • 执行 beforeUpdate 钩子
    • 执行 patch 将 subTree 渲染到 container,更新会有新旧diff
    • 执行 update 钩子

unmount

卸载:
卸载就是把不能自动回收的信息进行手动释放,防止内存泄露。

// 卸载
const unmount = (
  vnode,  // 需卸载的vnode
  parentComponent,
  parentSuspense,
  doRemove = false,  // 是否需要从dom树移除对应的节点
  optimized = false
) => {
  const {
    type,
    props,
    ref,
    children,
    dynamicChildren,
    shapeFlag,    // vnode形态标识
    patchFlag,    // vnode打补丁优化类型标识
    dirs,
  } = vnode
  // 对于非空vnode引用ref,进行卸载前重置,GC回收过期ref信息
  if (ref != null) {
    setRef(ref, null, parentSuspense, null)
  }
  if (shapeFlag & 256 /* COMPONENT_SHOULD_KEEP_ALIVE */) {
    parentComponent.ctx.deactivate(vnode)
    return
  }
  // 是否需要执行vnode中的自定义指令
  const shouldInvokeDirs = shapeFlag & 1 /* ELEMENT */ && dirs
  let vnodeHook
  // 执行props中传入的即将卸载钩子
  if ((vnodeHook = props && props.onVnodeBeforeUnmount)) {
    invokeVNodeHook(vnodeHook, parentComponent, vnode)
  }
  if (shapeFlag & 6 /* COMPONENT */) {
    // 组件vnode卸载逻辑
    unmountComponent(vnode.component, parentSuspense, doRemove)
  } else {
    if (shapeFlag & 128 /* SUSPENSE */) {
      // 卸载suspense,因为特殊有自动的一套逻辑
      vnode.suspense.unmount(parentSuspense, doRemove)
      return
    }
    if (shouldInvokeDirs) {
      // 执行自定义指令的生命周期
      invokeDirectiveHook(vnode, null, parentComponent, 'beforeUnmount')
    }
    if (
      dynamicChildren &&
      (type !== Fragment ||
       (patchFlag > 0 && patchFlag & 64) /* STABLE_FRAGMENT */)
    ) {
      // unmountChildren就是把传入的children遍历执行一遍unmount
      unmountChildren(
        dynamicChildren,
        parentComponent,
        parentSuspense,
        false,
        true
      )
    } else if (
      (type === Fragment &&
       (patchFlag & 128 /* KEYED_FRAGMENT */ ||
        patchFlag & 256) /* UNKEYED_FRAGMENT */) ||
      (!optimized && shapeFlag & 16) /* ARRAY_CHILDREN */
    ) {
      // 做对应的卸载操作
      unmountChildren(children, parentComponent, parentSuspense)
    }
    if (
      shapeFlag & 64 /* TELEPORT */ &&
      (doRemove || !isTeleportDisabled(vnode.props))
    ) {
      // 卸载teleport内置组件和suspense类似,也有自己的一套mount、unmount逻辑
      vnode.type.remove(vnode, internals)
    }
    // vnode对应整棵子树的信息都已卸载回收完毕后,
    // 将vnode对应的dom子树整体从dom树中移除掉,
    // 此时移除dom子树是安全的,无内存泄漏风险
    if (doRemove) {
      remove(vnode)
    }
  }
}

该方法主要做了3件事:

  • 找到所有动态节点,将GC无法自动回收的手动清理掉
  • 执行卸载生命周期钩子
  • 移除dom

unmountComponent

卸载所有子组件:

const unmountComponent = (instance, parentSuspense, doRemove) => {
  if (instance.type.__hmrId) {
    unregisterHMR(instance)
  }
  const { bum, effects, update, subTree, um } = instance
  // beforeUnmount 钩子
  if (bum) {
    invokeArrayFns(bum)
  }
  // 组件的effect,需要统一回收处理,并取消effect的激活状态
  if (effects) {
    for (let i = 0; i < effects.length; i++) {
      stop(effects[i])
    }
  }
  if (update) {
    stop(update)
    // 卸载整个subTree,subTree可以理解为渲染vnode,对实际渲染的vnode自然要做对应的unmount操作
    unmount(subTree, instance, parentSuspense, doRemove)
  }
  // unmounted 钩子,推入任务队列,等待渲染任务执行完毕后批量执行
  if (um) {
    queuePostRenderEffect(um, parentSuspense)
  }
  queuePostRenderEffect(() => {
    instance.isUnmounted = true
  }, parentSuspense)
  //在挂起暂挂中包含异步dep的组件
  //它的异步dep解析。这将从悬念中移除dep,并且
  //如果这是最后一个dep,立即解决悬念。
  if (
    parentSuspense &&
    parentSuspense.pendingBranch &&
    !parentSuspense.isUnmounted &&
    instance.asyncDep &&
    !instance.asyncResolved &&
    instance.suspenseId === parentSuspense.pendingId
  ) {
    parentSuspense.deps--
    if (parentSuspense.deps === 0) {
      parentSuspense.resolve()
    }
  }
  {
    devtoolsComponentRemoved(instance)
  }
}

😰 还有好多,有空继续更新。。。

createAppAPI

不用气馁,精彩还在后面!!!
我们回到 createRenderer ,看到 createApp 是 createAppAPI 生成的。
😀 来来来,继续往下看:

function createAppContext() {
  return {
    app: null,
    config: {
      isNativeTag: NO,  // 默认 const NO = () => false
      performance: false,
      globalProperties: {},
      optionMergeStrategies: {},
      isCustomElement: NO,
      errorHandler: undefined,
      warnHandler: undefined,
    },
    mixins: [],
    components: {},
    directives: {},
    provides: Object.create(null),
  }
}

// 是个高阶函数,返回createApp
function createAppAPI(render, hydrate) {
    return function createApp(rootComponent, rootProps = null) {
      // rootComponent 就是传入的options
      const context = createAppContext()
      const installedPlugins = new Set()
      let isMounted = false
      // 这么创建一个app实例,并返回。这就是我们看到的那个app
      const app = (context.app = {
        _uid: uid$1++,
        _component: rootComponent,
        _props: rootProps,
        _container: null,
        _context: context,  // 就是上面的createAppContext
        version,
        get config() {
          return context.config
        },
        set config(v) {
          {
            warn(
              `app.config cannot be replaced. Modify individual options instead.`
            )
          }
        },
        // 这里加载插件,和vue2的区别是,vue2加载插件是全局的,这里是每个createApp都独立。
        use(plugin, ...options) {
          // 判断是否加载过了
          if (installedPlugins.has(plugin)) {
            warn(`Plugin has already been applied to target app.`)
          } else if (plugin && isFunction(plugin.install)) {
            // 插件入口install
            installedPlugins.add(plugin)
            plugin.install(app, ...options)
          } else if (isFunction(plugin)) {
            installedPlugins.add(plugin)
            plugin(app, ...options)
          }
          return app
        },
        // 引入mixin
        mixin(mixin) {
          {
            if (!context.mixins.includes(mixin)) {
              context.mixins.push(mixin)
              // global mixin with props/emits de-optimizes props/emits
              // normalization caching.
              if (mixin.props || mixin.emits) {
                context.deopt = true
              }
            }
          }
          return app
        },
        // 引入组件 | 加载组件(与options下的component一样)
        component(name, component) {
          // 校验组件命名和引入名是否一致
          {
            validateComponentName(name, context.config)
          }
          if (!component) {
            return context.components[name]
          }
          // 已经注册过了,出现警告
          if (context.components[name]) {
            warn(
              `Component "${name}" has already been registered in target app.`
            )
          }
          // 注册组件
          context.components[name] = component
          return app
        },
        // 引入指令
        directive(name, directive) {
          {
            validateDirectiveName(name)
          }
          if (!directive) {
            return context.directives[name]
          }
          if (context.directives[name]) {
            warn(
              `Directive "${name}" has already been registered in target app.`
            )
          }
          context.directives[name] = directive
          return app
        },
        // 挂载
        mount(rootContainer, isHydrate) {
          // rootContainer 就是 #app的dom节点
          // 判断是否已挂载
          if (!isMounted) {
            // 创建虚拟节点
            const vnode = createVNode(rootComponent, rootProps)
            // 在根VNode上存储context
            vnode.appContext = context
            // 热更新
            {
              context.reload = () => {
                render(cloneVNode(vnode), rootContainer)
              }
            }

            if (isHydrate && hydrate) {
              hydrate(vnode, rootContainer)
            } else {
              // 这里判断渲染方式,浏览器是render。
              render(vnode, rootContainer)
            }
            isMounted = true
            app._container = rootContainer
            rootContainer.__vue_app__ = app
            // 加载开发工具
            {
              devtoolsInitApp(app, version)
            }
            return vnode.component.proxy
          } else {
            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)\``
            )
          }
        },
        // 卸载
        unmount() {
          if (isMounted) {
            render(null, app._container)
            {
              devtoolsUnmountApp(app)
            }
          } else {
            warn(`Cannot unmount an app that is not mounted.`)
          }
        },
        // vue的注入
        provide(key, value) {
          if (key in context.provides) {
            warn(
              `App already provides property with key "${String(key)}". ` +
                `It will be overwritten with the new value.`
            )
          }
          context.provides[key] = value
          return app
        },
      })
      return app
    }
  }

干货也太干了。我们用一张图说明调用逻辑:
图片来自 @望舒(mobai-epd1x)

Vue3 挂载原理分析 - 图1

手写流程

const createAppAPI = () => {

}

const createRenderer = (options) => {
  const render = (node, container) => {
    return { render, createApp: createAppAPI(render) }
  }
}

const renderer = createRenderer({})

const Vue = {
  createApp(options){
    return renderer.createApp(options)
  }
}