This article follows the previous entry function with a simple example (we will call it this example) to parse the operation after the mount in detail (parsing the options parameter passed in, compiling the template template, generating the virtual DOM object, parsing the virtual DOM object to generate the real DOM, how to trigger the DOM re-rendering after the data modification, etc.).

Simple example (added with Vue3setupMethod as an example)

<div id='app'>
  {{message}}
  <button @click="modifyMessage">Modify the data</button>
</div>
Copy the code
const { ref, createApp } = Vue
const app = createApp({
  // Vue3 added setup property
  setup(props) {
    const message = ref('Test data')
    const modifyMessage = () = > {
      message.value = 'Modified test data'
    }
    return {
      message,
      modifyMessage
    }
  }
}).mount('#app')
Copy the code

app.mount

const proxy = mount(container, false, container instanceof SVGElement)
Copy the code

Vue3 creates the app object with the createApp method, and then calls the app.mount method to get the root node. Then calls the original mount method to mount the root node

// Generate the context object
const context = createAppContext()
// The original mount functionmount( rootContainer: HostElement, isHydrate? : boolean, isSVG? : boolean ): any {if(! isMounted) {const vnode = createVNode(
      rootComponent as ConcreteComponent,
      rootProps
    )
    vnode.appContext = context
    // ...
    if (isHydrate && hydrate) {
      hydrate(vnode as VNode<Node, Element>, rootContainer as any)
    } else {
      render(vnode, rootContainer, isSVG)
    }
    isMounted = true
    app._container = rootContainer
    (rootContainer as any).__vue_app__ = app
    // ...
    returnvnode.component! .proxy } }Copy the code

The mount method first calls the createVNode method to create a vNode object. Briefly take a look at the internal implementation of the createVNode method (mainly to see the generation of the shapeFlag variable, associated with the subsequent call to the patch method).

export const createVNode = (
  __DEV__ ? createVNodeWithArgsTransform : _createVNode
) as typeof _createVNode

function _createVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag: number = 0,
  dynamicProps: string[] | null = null,
  isBlockNode = false
) :VNode {
  // ...
  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
  // ...
  return createBaseVNode(
    type,
    props,
    children,
    patchFlag,
    dynamicProps,
    shapeFlag,
    isBlockNode,
    true)}/ / createBaseVNode method
function createBaseVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag = 0,
  dynamicProps: string[] | null = null,
  shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
  isBlockNode = false,
  needFullChildrenNormalization = false
) {
    const vnode = {
        type,
        key,
        props,
        shapeFlag,
        patchFlag,
        // ...
    }
    // ...
    return vnode
}
Copy the code

You can see that shapeFlag is determined by the type of argument passed in to the rootComponent object that calls this method, the createApp method. In this case we are passing in an object, so the value of shapeFlag is shapeflags.stateful_component (1 << 2 = 4). Then set the appContext property of vNode to the Context object (this object is generated by calling createAppContext and will be parsed later when using the context object properties). Let’s go back to the mount method

if (isHydrate && hydrate) {
  hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
  render(vnode, rootContainer, isSVG)
}
Copy the code

Since isHydrate is passed in as false, the render method is called with the vNode object, root root node, and false(isSVG is the value of container Instanceof SVGElement, not an SVG element in this case). The render function called is a pass-through to the createAppAPI method, which is called in the baseCreateRenderer method. Go back to the baseCreateRenderer method and look at the Render method

const render: RootRenderFunction = (vnode, container, isSVG) = > {
  // VNode exists
  if (vnode == null) {
    if (container._vnode) {
      unmount(container._vnode, null.null.true)}}else {
    patch(container._vnode || null, vnode, container, null.null.null, isSVG)
  }
  // ...
}
Copy the code

Use patch to resolve the root component

Since vNode exists, the patch method is called

patch(null, vnode, container, null.null.null.false)

/ / patch method
const patch: PatchFn = (
  n1,
  n2,
  container,
  anchor = null,
  parentComponent = null,
  parentSuspense = null,
  isSVG = false,
  slotScopeIds = null,
  optimized = __DEV__ && isHmrUpdating ? false:!!!!! n2.dynamicChildren) = > {
  // ...
  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:
        if (shapeFlag & ShapeFlags.ELEMENT) {
          // ...
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
          processComponent(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
        } else if (shapeFlag & ShapeFlags.TELEPORT) {
          // ...
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
          // ...
        } else if (__DEV__) {
          warn('Invalid VNode type:', type, ` (The ${typeof type}) `)}}}Copy the code

The patch method selects the function to call based on the value of type, which in this case is {setup: Setup (props) {}} is an object, so enter the default branch, because the vnode shapeFlag just resolved as 4 (4 & (1 < < 2 | 1 < < 1) = 4), so call processComponent method (processing components)

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) {
        // The component is keep-alive
        if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {}
        else {
            mountComponent(
              n2,
              container,
              anchor,
              parentComponent,
              parentSuspense,
              isSVG,
              optimized
            )
      }
    } else{}}Copy the code

Mount the root component

Then call the mountComponent method

const mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) = > {
    const compatMountInstance =
      __COMPAT__ && initialVNode.isCompatRoot && initialVNode.component
    const instance: ComponentInternalInstance =
      compatMountInstance ||
      (initialVNode.component = createComponentInstance(
        initialVNode,
        parentComponent,
        parentSuspense
      ))
    // ...
    // resolve props and slots for setup context
    if(! (__COMPAT__ && compatMountInstance)) {if (__DEV__) {
        startMeasure(instance, `init`)
      }
      setupComponent(instance)
      if (__DEV__) {
        endMeasure(instance, `init`)}}// ...
    setupRenderEffect(
      instance,
      initialVNode,
      container,
      anchor,
      parentSuspense,
      isSVG,
      optimized
    )
    // ...
}
Copy the code

Internally, instance objects are created using the createComponentInstance method (create an Object and add a _ attribute to it using Object.defineProperty, which is the value of instance Object, The vNode property of the object is the vNode passed in when the call is made. There are some other attributes, which will be explained in more detail later). Then call the setupComponent(instance) method.

export function setupComponent(
  instance: ComponentInternalInstance,
  isSSR = false
) {
  // ...

  const setupResult = isStateful
    ? setupStatefulComponent(instance, isSSR)
    : undefined
  isInSSRComponentSetup = false
  return setupResult
}

function setupStatefulComponent(instance: ComponentInternalInstance, isSSR: boolean) {
    // ...
    instance.accessCache = Object.create(null)
    instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
    // 2. call setup()
    const { setup } = Component
    if (setup) {
        // ...
        const setupResult = callWithErrorHandling(
          setup,
          instance,
          ErrorCodes.SETUP_FUNCTION,
          [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
        )
        // ...}}Copy the code

The setupComponent method calls the setupStatefulComponent method, which internally uses a new Proxy() for instance. CTX with attributes like _, $, and $el. And agent of hook function object is PublicInstanceProxyHandlers

export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
    get({ _: instance }: ComponentRenderContext, key: string) {},
    set(
        { _: instance }: ComponentRenderContext,
        key: string,
        value: any
      ): boolean {},
    has({ _: { data, setupState, accessCache, ctx, appContext, propsOptions } }: ComponentRenderContext, key: string){}}Copy the code

PublicInstanceProxyHandlers contains a get, set, from the three hooks trap, subsequent trigger when we parse it in detail. Back in the setupStatefulComponent function, when the setup property exists (in this case), start running the setup function to parse the data and methods in the function body.

conclusion

In the original mount method, the createVNode is used to create the vNode. The parameter type is passed in by calling createApp. The shapeFlag is resolved based on the type of type. Then call the patch method and determine which method to call according to the value of shapeFlag. The mountComponent method is used to create the instance object. The CTX property of the object is added with attributes such as _, $, and $EL. The vNode property of the instance object is the created vNode. The Component property of a VNode object is instance, and the two are related. Then use the new Proxy to Proxy instance. CTX and set the get, set, and HAS hook functions. You’ll then parse the setup method to see how the hook function is set for the data passed in.