preface

The first step of mount was explained earlier. This article will start from the second step of mount apiCreateApp file under the run-time core folder, and then explain the rest of the steps. According to the main process of core code interpretation, the jump may be relatively large, but in the process of explanation will indicate which file.

The body of the

The text begins here, the mount begins here. Let’s take a look at the source code, the previous DOM part of the source code is not said here, from the core part of the start:

packages/runtime-core/src/apiCreateApp.ts

mount(rootContainer: HostElement, isHydrate? :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)
    }
    isMounted = trueapp._container = rootContainer ; (rootContaineras any).__vue_app__ = app
    
    returnvnode.component! .proxy } }Copy the code

The source code here is actually quite simple,

  • Call createVNode to get vNode, rootComponent is the config data passed when createApp(config) is called, rootProps is root props. RootProps is null;
  • Save the context on the following node;
  • Call the render function, only render;
  • Set isMounted to true.
  • The _container of the instance is saved as the current rootContainer;
  • RootContainer adds attribute __vue_app__ to the current app instance;
  • Returns the proxy for vnode.ponent.

The core render code is the render function.

render

The render function is completely different in Vue2 and Vue3,

  • Vue2 in the render function is to do the specific work, is the real render operation, return the result is vnode, can be in this review under Vue2 source interpretation (seven)-mount;
  • The render function in Vue3 does distribution work, which is equivalent to one router, two lines, unmount and patch, with no return result.

Take a look at render’s source code:

packages/runtime-core/src/renderer.ts

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

The above code for the render function source:

  • Parameter 1: vnode, which is the vnode to be updated to the page, obtained by createVNode above; Container is a container for presentation.
  • If container._vnode has a value (i.e., the previous DOM rendering), unmount it.
  • If the vNode is not empty, patch, DOM diff, and render are performed.
  • The flushPostFlushCbs function is executed, the scheduler is called back, and the Promise implementation is used. The difference with Vue2 is that Vue2 is handled by macro or micro tasks
  • The _vnode of the container is stored as the current Vnode for dom diff operations. This operation is the same as that in Vue2.

Since it is rendering, vNode will not be empty. It will definitely go to the patch function.

packages/runtime-core/src/renderer.ts

const patch: PatchFn = (
    n1, // old
    n2, // new
    container, / / container
    anchor = null,
    parentComponent = null,
    parentSuspense = null,
    isSVG = false,
    optimized = false
) = > {
    // If type is not the same, unmount n1 directly
    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,
                optimized
            )
            break
        default:
            if (shapeFlag & ShapeFlags.ELEMENT) {
                processElement(
                    n1,
                    n2,
                    container,
                    anchor,
                    parentComponent,
                    parentSuspense,
                    isSVG,
                    optimized
                )
            } else if (shapeFlag & ShapeFlags.COMPONENT) {
                processComponent(
                    n1,
                    n2,
                    container,
                    anchor,
                    parentComponent,
                    parentSuspense,
                    isSVG,
                    optimized
                )
            } else if(shapeFlag & ShapeFlags.TELEPORT) { ; (type as typeof TeleportImpl).process(
                    n1 as TeleportVNode,
                    n2 as TeleportVNode,
                    container,
                    anchor,
                    parentComponent,
                    parentSuspense,
                    isSVG,
                    optimized,
                    internals
                )
            } else if(__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { ; (type as typeof SuspenseImpl).process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals)
            } else if (__DEV__) {
                warn('Invalid VNode type:'.type.` (The ${typeof type}) `)}}if(ref ! =null && parentComponent) {
        setRef(ref, n1 && n1.ref, parentComponent, parentSuspense, n2)
    }
}
Copy the code

The patch function has been combed and analyzed for the entrance, and the figure below is obtained, in which there are several commonly used lines:

  • ProcessFragment: a function that handles fragments (DOM arrays);
  • ProcessElement: a function that processes elements;
  • ProcessComponent: Functions that process components;

Next we’ll look at an example involving processFragment and processElement, doing a DOM diff operation;

Render example

We’re going to go through an example now, and we’re going to go through the functions that we use, from beginning to end. Suppose we now have a list:

packages/vue/examples/classic/hello.js

const app = Vue.createApp({
    data() {
        return {
            list: ['a'.'b'.'c'.'d']}}}); app.mount('#demo')
Copy the code

packages/vue/examples/classic/hello.html

<! DOCTYPEhtml>
<html>
<head>
    <meta name="viewport"  content="Initial - scale = 1.0, the maximum - scale = 1.0, the minimum - scale = 1.0, user-scalable=no,target-densitydpi=medium-dpi,viewport-fit=cover"/>
    <title>Vue3.js hello example</title>
    <script src=".. /.. /dist/vue.global.js"></script>
</head>
<body>
<div id="demo">
    <ul>
        <li v-for="item in list" :key="item">
            {{item}}
        </li>
    </ul>
</div>
<script src="./hello.js"></script>
</body>
</html>
Copy the code

Create hello.js and hello. HTML files in the Vue3 source code root directory, copy the above code into the object file, and run NPM run dev in the root directory. Then open the browser, enter the url address: file:///Users/draven/mywork/vue-3.0.0/packages/vue/examples/classic/hello.html; You can see the rendering of the page:

Now that we’re done, let’s take a look at how the effects on the page work, starting with the render function above.

  • 1. Start running: callrender(vnode, rootContainer), the function runs atpackages/runtime-core/src/apiCreateApp.tsThe render function declaration is located inpackages/runtime-core/src/renderer.ts; The parameter vnode is generated by calling createVNode above, and the parameter rootContainer is the element with id demo we passed in above.
  • 2. The next entry ispackages/runtime-core/src/renderer.tsFile, the following functions are mostly in this file, if there are special cases will be explained.
  • Next run: call inside the render functionpatch(container._vnode || null, vnode, container)
    • 3.1. The first parameter is the old vnode, because it is the first rendering, the old vnode does not exist, so null; The second parameter is the pass-through Vnode; The third argument is transparent container(#demo);
    • 3.2 Patch function also accepts other parameters, but we are not using them for the time being:patch(n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, optimized = false); N1 is null, n2 is the vnode to be updated, and container is passthrough #demo.
    • N1 is null and n2 is currently an object:

The judgment will be consistentshapeFlag & ShapeFlags.COMPONENTAnd went toprocessComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)Functions; N1 is null. N2 is shown in the figure. Container is #demo.

  • If n1 is not null, the processComponent function will call updateComponent. At this point, we’re rendering for the first time, so we’re not going to do update, we’re going to do another logic; If the keepAlive component is a keepAlive component, go to the Activate logic. At this point, we are not using the mountComponent for keepalive, so go to the mountComponent function,mountComponent(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)Container is #demo. Other parameters are currently null(false) by default.
  • The mountComponent function first calls createComponentInstance to generate an instance of n2. Then it calls setupComponent to initialize props and slotssetupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized )InitialVNode is n2,container is #demo, and others are default values.
  • 6. SetupRenderEffect is a core function that will mount the update method for the current instance. The update method is generated by effect. After update is generated, the generated effect method is run before mounting, and the current effect method is returned to update. Effect takes two parameters. The first parameter is componentEffect, which calls componentEffect to listen for changes. It says to run the generated Effect method, the generated effect method calls this componentEffect function inside;
  • Instance.ismounted (); instance.isMounted (); If already rendered, go to update logic; We haven’t rendered yet, then go unrendered logic; Take a look at the source code for this part.
    function componentEffect() {
      if(! instance.isMounted) {let vnodeHook: VNodeHook | null | undefined
        const {el, props} = initialVNode
        const {bm, m, parent} = instance
    
        // beforeMount hook
        if (bm) {
            invokeArrayFns(bm)
        }
        // onVnodeBeforeMount
        if ((vnodeHook = props && props.onVnodeBeforeMount)) {
            invokeVNodeHook(vnodeHook, parent, initialVNode)
        }
        const subTree = (instance.subTree = renderComponentRoot(instance))
    
        if (el && hydrateNode) {
            hydrateNode(
                initialVNode.el as Node,
                subTree,
                instance,
                parentSuspense
            )
        } else {
            patch(
                null,
                subTree,
                container,
                anchor,
                instance,
                parentSuspense,
                isSVG
            )
            initialVNode.el = subTree.el
        }
        if (m) {
            queuePostRenderEffect(m, parentSuspense)
        }
        if ((vnodeHook = props && props.onVnodeMounted)) {
            queuePostRenderEffect(() = >{ invokeVNodeHook(vnodeHook! , parent, initialVNode) }, parentSuspense) }const {a} = instance
        if (
            a &&
            initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
        ) {
            queuePostRenderEffect(a, parentSuspense)
        }
        instance.isMounted = true
      } else {
        // no first render}}Copy the code

Above is collated after the first rendering componentEffect function source;

    • 7.1. Call the beforeMount hook function of the current instance.
    • 7.2. Call the BeforeMount hook function of n2’s parent.
    • 7.3. Call renderComponentRoot to render the root element of the component;
    • 7.4. Call patch:patch(null, subTree, container, anchor, instance, parentSuspense, isSVG); The value of subtree is:; The container # for the demo; Anchor is null, instance is the current instance, parentSuspense is null, isSVG is false;
    • 7.5. Invoke the mounted hook function of the current instance. The mounted hook function of n2 is called. Call the activated hook function of the current instance. Call queuePostRenderEffect instead of calling it directly;
    • 7.6. Set isMounted to true.
  • 8. Calling patch in the above componentEffect function is the beginning of formal rendering, most of which are equivalent to data sorting:
    • 8.1. Transfer to patch function according to the operation parameters of the above componentEffect function:patch(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized); At this point, n1 is null, n2 is subtree, container is still #demo, Anchor is null, parentComponent is instance above, parentSuspense is null, isSVG is false, Optimized to false;
    • As you can see in the figure above, component gets the instance subtree type Fragment and goes to the processFragment function.
    • 8.3. The parameters accepted by processFragment are the same as those accepted by patch.
        const processFragment = (
            n1: VNode | null,
            n2: VNode,
            container: RendererElement,
            anchor: RendererNode | null,
            parentComponent: ComponentInternalInstance | null,
            parentSuspense: SuspenseBoundary | null,
            isSVG: boolean,
            optimized: boolean
        ) = > {
            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 as VNodeArrayChildren,
                    container,
                    fragmentEndAnchor,
                    parentComponent,
                    parentSuspense,
                    isSVG,
                    optimized
                )
            } else {
            	 // Other logic}}Copy the code

      The argument tells us that it will go to the current if logic and insert the skeleton first; Then performmountChildrenSubtree = subtree; subtree = subtree; subtree = subtree;

      const mountChildren: MountChildrenFn = (
          children,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized,
          start = 0
      ) = > {
          for (let i = start; i < children.length; i++) {
              const child = (children[i] = optimized
                  ? cloneIfMounted(children[i] as VNode)
                  : normalizeVNode(children[i]))
              patch(
                  null,
                  child,
                  container,
                  anchor,
                  parentComponent,
                  parentSuspense,
                  isSVG,
                  optimized
              )
          }
      }
      Copy the code

      You can see that n2. Children will be traversed, and n2. Children has only one element, ul

    • 8.4. Call using the above runtime parameterspatch(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG = false, optimized); Parameter: n1 is null; Child is ul mentioned above; Container is #demo, anchor is fragmentEndAnchor inside processFragment function above; ParentComponent is instance; ParentSuspense is null; IsSVG is false; Optimized is true because changes were made in processFragment above;
    • The parameter of processElement is the same as that of patch. The parameter of processElement is the same as that of patch.
      const processElement = (
          n1: VNode | null,
          n2: VNode,
          container: RendererElement,
          anchor: RendererNode | null,
          parentComponent: ComponentInternalInstance | null,
          parentSuspense: SuspenseBoundary | null,
          isSVG: boolean,
          optimized: boolean
      ) = > {
          isSVG = isSVG || (n2.type as string) = = ='svg'
          if (n1 == null) {
              mountElement(
                  n2,
                  container,
                  anchor,
                  parentComponent,
                  parentSuspense,
                  isSVG,
                  optimized
              )
          } else {
              patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized)
          }
      }
      Copy the code

      If n1 is null, the mountElement logic will be used and the parameter will not be changed. When mountElement is executed, ul children will be checked. If ul children has a value, the mountChildren function will be called:

      mountChildren(
          vnode.children as VNodeArrayChildren,
          el,
          null,
          parentComponent,
          parentSuspense,
          isSVG && type! = ='foreignObject', optimized || !! vnode.dynamicChildren )Copy the code

      Vnode. children is an array of four Li’s. El is ul, anchor is null, parentComponent is instance; ParentSuspense is null; IsSVG is false; Optimized to true; Repeat the mountChildren function above;

    • 8.6. If the type of li in the mountChildren function is li, the processElement is executed.
  • 9. All the above steps have been completed and the data is now rendered on the page.
  • At this time basically all things are done, that is, equivalent to the main queue is free, callflushPostFlushCbs()Start executing functions in the queue;
  • 11. Finally, point the _vNode attribute of the container to the current vNode. So you can do dom diff next time.
  • 12. First render run completed.

conclusion

This chapter focuses on the rendering process of first render, and the next chapter will explain the patch part of Vue3 in combination with the DOM Diff part of Vue2 source code Interpretation (7) in accordance with the rhythm of this chapter, and slowly digest them all.