Welcome to follow our wechat official account: Yang Yitao

In the previous analysis of patch function, we knew that different functions were called to compare the differences between the old and new virtual nodes and to smooth out the differences through different types of judgment. At that time, part of the function implementation details of patch function call were also introduced. This paper will take you to analyze most of the source code implementation of processElement and processComponent functions, and summarize the core work flow of Patch function with a flow chart at the end of the paper. As for the specific implementation of diff function, as a difficulty, it will be explained in depth in the next article.

processElement

Let’s look at the code implementation of processElement:

// Snippet 1
const processElement = (
    n1: VNode | null,
    n2: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean
  ) = > {
    isSVG = isSVG || (n2.type as string) = = ='svg'
    if (n1 == null) {
      mountElement(
        n2,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    } else {
      patchElement(
        n1,
        n2,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    }
  }
Copy the code

For now, we will focus only on the first four parameters of processElement.

  1. n1: oldVirtual Node;
  2. n2New:Virtual Node;
  3. container: Virtual NodeintoReal NodeTo be mounted laterDOMElements;
  4. anchor:Virtual NodeintoReal NodeTo be mounted laterDOMWhere exactly on the element.

Now that we know about the parameters, let’s look at conditional judgments:

// Snippet 2
if (n1 == null) {
      mountElement(
        // Omit some code here...)}else {
      patchElement(
        // Omit some code here...)}Copy the code

If the old virtual Node is null, it indicates that the old virtual Node does not exist, so there is no need to perform the so-called comparison. Simply call mountElement to mount the new virtual Node (n2) to the container Node. If the old virtual Node is not null, it indicates that the old Node exists, so we need to compare the difference between the two and use the code implementation with good performance to erase the difference, and the patchElement function has such ability. Next, we start to analyze the two functions of mountElement and patchElement.

mountElement

MountElement (mountElement)

// Snippet 3
const mountElement = (
    vnode: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean
  ) = > {
    let el: RendererElement
    let vnodeHook: VNodeHook | undefined | null
    const { type, props, shapeFlag, transition, patchFlag, dirs } = vnode
    if(! __DEV__ && vnode.el && hostCloneNode ! = =undefined &&
      patchFlag === PatchFlags.HOISTED
    ) {
      el = vnode.el = hostCloneNode(vnode.el)
    } else {
      el = vnode.el = hostCreateElement(
        vnode.type as string,
        isSVG,
        props && props.is,
        props
      )
      if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
        hostSetElementText(el, vnode.children as string)}else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        mountChildren(
          vnode.children as VNodeArrayChildren,
          el,
          null,
          parentComponent,
          parentSuspense,
          isSVG && type! = ='foreignObject',
          slotScopeIds,
          optimized
        )
      }
      // Some code omitted here...
    }
    // Some code omitted here...
    hostInsert(el, container, anchor)
    // Some code omitted here...
  }
Copy the code

The implementation logic inside mountElement is quite rich, but to highlight the main thread, I have omitted the code that calls back instructions to declare periodic functions, handles animations and asynchrony, and adds attributes to the created DOM elements, which will be explained in this article on a specific topic. From code snippet 3, we can assume that mountElement does the following:

  1. According to incomingVirtual NodeCreate the correspondingReal NodeThe elementel;
  2. ifelIf the child element is text, the text is set to that element, or if the child element is an arraymountChildrenThe function mounts each child element, and mounting to the container is what we created hereelOf course, if the child elements of the child elements are still arrays, the recursion will continue until there are no children;
  3. willelMount to thecontainerElement.

The first if condition judgment for snippet 3 can be confusing:

// Snippet 4
if(! __DEV__ && vnode.el && hostCloneNode ! = =undefined &&
      patchFlag === PatchFlags.HOISTED
    ) {
      el = vnode.el = hostCloneNode(vnode.el)
    }
Copy the code

As we mentioned earlier, mounting means adding the created DOM element to the target DOM node. In fact, Vue3 does static variable enhancement and optimization to a certain extent during compilation process, so there is a judgment condition here. Related content will be introduced in compilation related articles, here is a brief understanding.

patchElement

Compared with mountElement, the logic of patchElement is much more complicated, because mountElement does not have the ability to find differences. You only need to create elements according to the virtual Node and mount them to the target Node. However, patchElement needs to find out the differences between the new virtual Node and the old virtual Node, and add, delete and modify the current DOM elements under the condition of good performance.

Since patchElement has a lot of content, even the key content is not small. For the sake of this description, I’ll break it down into several aspects, presenting parts of the code as separate code blocks. It mainly involves the following aspects:

  1. patchElementThe core logic of
  2. patchBlockChildrenpatchChildrenRespective duties
  3. patchFlag

patchElementCore functions of

As mentioned before, the core function of patchElement is to find and smooth differences. But what is the difference between finding and smoothing out? As the name implies, the object of patchElement operation is DOM element, and a DOM element actually contains two chunks of content. The first one is various attribute states of the DOM element itself. The second block is the child of the DOM element. The core function of patchElement is to use patchChildren and patchProps to find and smooth the differences of child elements and the attributes of the current DOM element respectively. Because Vue3 is internally optimized, it is not always necessary to call patchChildren and patchProps, but patchBlockChildren or other functions to complete the relevant work.

Of course, in addition to the core functions, there are branch functions, branch functions including call instructions and virtual Node corresponding and update-related life cycle functions and some asynchronous process processing, after introducing the core process, there will be a special article on related content.

patchBlockChildrenpatchChildren

We only need to know that patchBlockChildren is related to optimization, and relevant content will be introduced at an appropriate time in subsequent articles, while patchChildren will not be covered in this paper, because this function can be said to be the soul of the whole patchElement function and its logic is complicated. The well-known DIff algorithm will also start with patchChildren function. Please look forward to the next article about the analysis of diff algorithm.

patchFlag

// Snippet 5
 if (patchFlag > 0) {
      if (patchFlag & PatchFlags.FULL_PROPS) {
        patchProps(
          el,
          n2,
          oldProps,
          newProps,
          parentComponent,
          parentSuspense,
          isSVG
        )
      } else {
        if (patchFlag & PatchFlags.CLASS) {
          if(oldProps.class ! == newProps.class) { hostPatchProp(el,'class'.null, newProps.class, isSVG)
          }
        }
        if (patchFlag & PatchFlags.STYLE) {
          hostPatchProp(el, 'style', oldProps.style, newProps.style, isSVG)
        }
        if (patchFlag & PatchFlags.PROPS) {
          const propsToUpdate = n2.dynamicProps!
          for (let i = 0; i < propsToUpdate.length; i++) {
            const key = propsToUpdate[i]
            const prev = oldProps[key]
            const next = newProps[key]
            if(next ! == prev || key ==='value') {
              hostPatchProp(
                el,
                key,
                prev,
                next,
                isSVG,
                n1.children as VNode[],
                parentComponent,
                parentSuspense,
                unmountChildren
              )
            }
          }
        }
      }
      if (patchFlag & PatchFlags.TEXT) {
        if(n1.children ! == n2.children) { hostSetElementText(el, n2.childrenas string)}}}else if(! optimized && dynamicChildren ==null) {
      patchProps(
        el,
        n2,
        oldProps,
        newProps,
        parentComponent,
        parentSuspense,
        isSVG
      )
    }
Copy the code

The content logic of code fragment 5 is quite clear, but one point needs to be mentioned, that is, the code in the form of patchFlag & PatchFlags.text, which is the same principle of ShapeFlags introduced in the last article. Let’s look at the code implementation of PatchFlags:

// Snippet 6
export const enum PatchFlags {
  TEXT = 1,
  CLASS = 1 << 1,
  STYLE = 1 << 2,
  PROPS = 1 << 3,
  FULL_PROPS = 1 << 4,
  HYDRATE_EVENTS = 1 << 5,
  STABLE_FRAGMENT = 1 << 6,
  KEYED_FRAGMENT = 1 << 7,
  UNKEYED_FRAGMENT = 1 << 8,
  NEED_PATCH = 1 << 9,
  DYNAMIC_SLOTS = 1 << 10,
  DEV_ROOT_FRAGMENT = 1 << 11,
  HOISTED = -1,
  BAIL = -2
}
Copy the code

From code fragment 6, it is not difficult to find that patchFlag represents the type of attribute to be operated. Meanwhile, from code fragment 5 combined with the introduction of bit operation in the previous article, it is not difficult to find that variable patchFlag can express multiple states simultaneously, such as class attribute and style attribute.

processComponent

Let’s look at the source code implementation of the processComponent function:

// Snippet 7
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) {
      if(n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) { ; (parentComponent! .ctxas KeepAliveContext).activate(
          n2,
          container,
          anchor,
          isSVG,
          optimized
        )
      } else {
        mountComponent(
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
      }
    } else {
      updateComponent(n1, n2, optimized)
    }
  }
Copy the code

This logic has a high degree of similarity to processComponent, except that it has keep-alive features for components, which are not covered in this article. Let’s look at the implementation in the mountComponent and updateComponent functions.

mountComponent

Let’s start with the mountComponent function:

// Snippet 8
const mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) = > {
    // Omit some code here...
    const instance: ComponentInternalInstance =
      compatMountInstance ||
      (initialVNode.component = createComponentInstance(
        initialVNode,
        parentComponent,
        parentSuspense
      ))
    // Omit some code here...
    setupRenderEffect(
      instance,
      initialVNode,
      container,
      anchor,
      parentSuspense,
      isSVG,
      optimized
    )
    // Omit some code here...
  }
Copy the code

After omitting some code, we leave behind the most critical code, which shows two key things about the mountComponent function:

  1. Through the functioncreateComponentInstanceCreate a component instance;
  2. In the functionsetupRenderEffectCreates a function that renders the child component for the component instance and passes toReactiveEffectInstance to enable the function to be re-executed when reactive data changes.

Let’s look at the createComponentInstance and setupRenderEffect functions.

createComponentInstance

// Snippet 9
export function createComponentInstance(
  vnode: VNode,
  parent: ComponentInternalInstance | null,
  suspense: SuspenseBoundary | null
) {
  // Omit some code here...
  const instance: ComponentInternalInstance = {
    // Omit some code here...
  }
  // Omit some code here...

  return instance
}
Copy the code

We need to know that a component instance is actually an object, corresponding to the object instance in snippet 9. Of course, since it is a component instance, it means that the parameter vnode represents a virtual Node of type component, which is then passed to setupRenderEffect as a parameter. Now we enter this function for analysis.

setupRenderEffect

First look at the relevant code implementation:

// Snippet 10
const setupRenderEffect: SetupRenderEffectFn = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) = > {
    const componentUpdateFn = () = > {
      // Omit some code here...
    }
    const effect = (instance.effect = new ReactiveEffect(
      componentUpdateFn,
      () = > queueJob(instance.update),
      instance.scope 
    ))

    const update = (instance.update = effect.run.bind(effect) as SchedulerJob)
    update.id = instance.uid
    // Omit some code here...
    update()
  }
Copy the code

In Snippet 10, there is a lot of simplification of the logic except for the most critical logic. SetupRenderEffect does three things:

  1. Define a functioncomponentUpdateFn;
  2. createReactiveEffectInstance of the function to be definedcomponentUpdateFnPassed as arguments to the constructor;
  3. theeffect.run.bind(effect)As a component instanceinstancetheupdateProperty value;

What happens when you do these three steps? The result is that the reactive data used in the function componentUpdateFn is re-executed when it changes. We know that the mountComponent function mounts the DOM tree of the component to the target node. The core role of the function componentUpdateFn is to convert a component instance into a real DOM tree and mount that DOM tree to the container node. For details, see below.

componentUpdateFn

// Snippet 11
   const componentUpdateFn = () = > {
      if(! instance.isMounted) {// Omit some code here...
        if (el && hydrateNode) {
          // Omit some code here...
        } else {
          // Omit some code here...
          const subTree = (instance.subTree = renderComponentRoot(instance))
          // Omit some code here...
          patch(
            null,
            subTree,
            container,
            anchor,
            instance,
            parentSuspense,
            isSVG
          )
          // Omit some code here...
          initialVNode.el = subTree.el
        }
        // Omit some code here...
        instance.isMounted = true
        // Omit some code here...
        initialVNode = container = anchor = null as any
      } else {
        // Omit some code here...
        if (next) {
          next.el = vnode.el
          updateComponentPreRender(instance, next, optimized)
        } else {
          next = vnode
        }
        // Omit some code here...
        const nextTree = renderComponentRoot(instance)
        // Omit some code here...
        const prevTree = instance.subTree
        instance.subTree = nextTree
        // Omit some code here...
        patch(
          prevTree,
          nextTree,
          // parent may have changed if it's in a teleporthostParentNode(prevTree.el!) ! .// anchor may have changed if it's in a fragment
          getNextHostNode(prevTree),
          instance,
          parentSuspense,
          isSVG
        )
        // Omit some code here...
        next.el = nextTree.el
        // Omit some code here...}}Copy the code

The function componentUpdateFn contains more than 200 lines of code, with a lot of streamlining in snippet 11. This function is the soul of component rendering and updating. If (! Instance. isMounted) {}else{} handles both mount and update.

Mount related logic

For mount operations, the function componentUpdateFn handles the server-side rendering logic that is not discussed in this article. Normally, two things are done for the mount operation:

  1. callrenderComponentRootFunction that takes the component instanceinstanceInto a childVirtual NodeTree and assign a value toinstance.subTree, and callpatchThe function takes that subVirtual NodeThe tree is mounted to the target container node.
  2. performinitialVNode.el = subTree.el, and the corresponding of the child nodeelNode assigned to componentVirtual NodetheelProperties.

Note here that the relationship between component type virtual Node and subTree is assumed to have the following code:

// Snippet 12 file index.vue<template>
    <App></App>
</template>
Copy the code
// Snippet 13 file app.vue<template>
    <div>Hello World</div>
</template>
Copy the code

Component virtual Node represents
, and subTree represents

Hello World

.

renderComponentRoot

Let’s go to renderComponentRoot and explore how to get a subTree from a component instance:

// Snippet 14
export function renderComponentRoot(
  instance: ComponentInternalInstance
) :VNode {
    // Omit some code here...
    if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
      constproxyToUse = withProxy || proxy result = normalizeVNode( render! .call( proxyToUse, proxyToUse! , renderCache, props, setupState, data, ctx ) ) fallthroughAttrs = attrs }// Omit some code here...
  return result
}
Copy the code

After skipping a lot of code, it’s easy to see that the core job of the function renderComponentRoot is to call the render function of the component via a proxy object. Why proxy objects? The answer was already answered in the previous article, one of the most important reasons is that access to ref values does not need to be in the.value format, and the other is to protect the content of the child component from being accessed by the parent component. As for the function of the render function we have also explained in the previous article, will not be repeated here.

Update related logic

With the mount logic analyzed above, the update logic is straightforward. It can be summarized as the following two steps:

  1. Get component newsubTreeAnd what we have right nowsubTree;
  2. callpatchFunction to perform the update operation.

updateComponent

Let’s look at the implementation of the updateComponent function:

// Snippet 15
const updateComponent = (n1: VNode, n2: VNode, optimized: boolean) = > {
    const instance = (n2.component = n1.component)!
    if (shouldUpdateComponent(n1, n2, optimized)) {
      if( __FEATURE_SUSPENSE__ && instance.asyncDep && ! instance.asyncResolved ) {// Omit some code here...
      } else {
        instance.next = n2
        invalidateJob(instance.update)
        instance.update()
      }
    } else {
      // no update needed. just copy over properties
      n2.component = n1.component
      n2.el = n1.el
      instance.vnode = n2
    }
  }
Copy the code

With that in mind, we can say that the core of updateComponent is to execute instance.update().

conclusion

Combined with the previous article, we can now say that we have understood the core workflow of Vue3’s rendering mechanism. Please look at this flow chart first:

Combining this flow chart with the content of the previous paper and this paper, we can have a clear understanding of theVirtual NodeintoReal NodeThe implementation process of. Please look forward to the next article ondiffDescription of the algorithm.

Write in the last

After reading the article, you can do the following things to support you:

  • ifLike, look, forwardCan let the article help more people in need;
  • If you are the author of wechat public account, you can find me to openWhite list.reprintedMy original articles;

Finally, please pay attention to my wechat public account: Yang Yitao, you can get my latest news.