Vue3 source code analysis 01-initialization rendering process

Time:2022-8-4
  1. Today's goals:
  • Understand the vue3 initialization rendering process

our code today

<div id='app'>
  <div>
    <span>{{ count }}</span>
    <button @click='add'>click me add</button>
  </div>
</div>
const {ref , h } = Vue
Vue.createApp({
  setup(p , c){
    const count = ref(0)
    const add = () => {
      count.value ++
    }
    return {
      count,
      add
    }
  },
}).mount('#app')

start analysis

createApp

Entry method: runtime-dom -&gt; index

From the above code, we can see that vue3 initialization needs to go through the createApp method, and then mount it to the selected container. So let's look at the implementation of the createApp method

// entry method
export const createApp = ((...args) => {
  // get the app instance
  const app = ensureRenderer().createApp(...args)

  const { mount } = app
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    // Verify that the container is legal
    const container = normalizeContainer(containerOrSelector)
    if (!container) return
    const component = app._component
    if (!isFunction(component) && !component.render && !component.template) {
      // append properties
      component.template = container.innerHTML
    }
    // clear the container contents
    container.innerHTML = ''
    // start mounting
    const proxy = mount(container, false, container instanceof SVGElement)

    return proxy
  }

  return app
}) as CreateAppFunction<Element>

We can see that the core purpose of the createApp method is to create an app, and after creation, add the mount method for subsequent chain calls.

Let's take a look at the app creation process

ensureRenderer

Create a renderer: runtime-dom -&gt; index

function ensureRenderer() {
  return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}

This method attempts to return a renderer. If the renderer does not exist, it will create a renderer. Look at the rendererOptions passed in when it is created

Vue3 source code analysis 01-initialization rendering process

Here he merges patchProp , forcePatchProp , nodeOps

Vue3 source code analysis 01-initialization rendering process

We can see that nodeOps are real DOM operations.

Let's take a look at the createRenderer method

createRenderer

Create a renderer: runtime-core -&gt; renderer

Vue3 source code analysis 01-initialization rendering process

As you can see, after doing several overloads here, the final execution is baseCreateRenderer

Vue3 source code analysis 01-initialization rendering process

This method is too long to take a full screenshot. In general, the ultimate purpose of this method is to get the execution result of render and createAppApi. Let's take a look at what createApp does.

createAppApi

Generate app instance to register global API: runtime-core -&gt; apiCreateApp

Vue3 source code analysis 01-initialization rendering process

  1. This method first creates an empty context of the app
export function createAppContext(): AppContext {
  return {
    app: null as any,
    config: {
      isNativeTag: NO,
      performance: false,
      globalProperties: {},
      optionMergeStrategies: {},
      errorHandler: undefined,
      warnHandler: undefined,
      compilerOptions: {}
    },
    mixins: [],
    components: {},
    directives: {},
    provides: Object.create(null),
    optionsCache: new WeakMap(),
    propsCache: new WeakMap(),
    emitsCache: new WeakMap()
  }
}

this structure

  1. At the same time, a collection of management plugins will be created
  2. In the app, some global related things are registered. (If you want to see global directives, global components, and plug-ins related, you can go to this method to see)
const app: App = (context.app = {
  _uid: uid++,
  _component: rootComponent as ConcreteComponent,
  _props: rootProps,
  _container: null,
  _context: context,
  _instance: null,
  version,
  get config() {
    return context.config
  },
  set config(v) {
    if (__DEV__) {
      warn(
        `app.config cannot be replaced. Modify individual options instead.`
      )
    }
  },
  use(plugin: Plugin, ...options: any[]) {
    // more ...
    return app
  },
  mixin(mixin: ComponentOptions) {
      // more ...
    return app
  },
  component(name: string, component?: Component): any {
    // more ...
    return app
  },
  directive(name: string, directive?: Directive) {
    // more ...
    return app
  },
  mount(
    rootContainer: HostElement,
    isHydrate?: boolean,
    isSVG?: boolean
  ): any {
    // more ...
  },
  unmount() {
    // more ...
  },
  provide(key, value) {
    // more ...
    return app
  }
})

This declares a mount method (remember!!!).

After that, he returned the app. So far, our app instance has been created. We can see that the design of vue3 is to extract platform-related operations, so that it is friendly to multi-platform framework developers. , just pay attention to the node operation of the corresponding platform and create a renderer.

We go back to the entry method

expose mount method

Vue3 source code analysis 01-initialization rendering process

After getting the app instance, vue caches the mount method in the instance and rewrites the mount method on the instance. When we call mount (the rewritten mount is called),

  1. Vue will first verify that the mount target you pass in is a legitimate target.
  2. He will try to get the \_component on the instance (the option registered when the current component is createApp)
  3. If it is found that the options we registered are not function || no render configuration || no template configuration, he will add template attribute to component, (this attribute will be used later!!!)
  4. Then empty the innerHTML in the container
  5. Execute the previously cached (in-instance) mount method

mount

Mount method: runtime-core -&gt; apiCreateApp -&gt; mount

Vue3 source code analysis 01-initialization rendering process

  1. If the current instance has not been mounted, it will create a vnode. Because of the reference relationship, the template attribute we added will also be in the rootComponent

Vue3 source code analysis 01-initialization rendering process

Take a look at the createVNode method

/**
 *  runtime-core -> vnode
 */

function _createVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, // rootComponent
  props: (Data & VNodeProps) | null = null, // null
  children: unknown = null, // null
  patchFlag: number = 0, // 0
  dynamicProps: string[] | null = null, // null
  isBlockNode = false // false
): VNode {
  // If the passed parameter is already a vnode
  // you don't need to convert a clone
  // Check if the child element is a valid child element
  if (isVNode(type)) {
    const cloned = cloneVNode(type, props, true /* mergeRef: true */)
    if (children) {
      normalizeChildren(cloned, children)
    }
    return cloned
  }

  // if it is a class component
  if (isClassComponent(type)) {
    // more
  }

  // handle class and style in props
  if (props) {
    // more ...
  }

  // mark
  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

  // vnode body
  const vnode: VNode = {
    __v_isVNode: true,
    __v_skip: true,
    type,
    props,
    key: props && normalizeKey(props),
    ref: props && normalizeRef(props),
    scopeId: currentScopeId,
    slotScopeIds: null,
    children: null,
    component: null,
    suspense: null,
    ssContent: null,
    ssFallback: null,
    dirs: null,
    transition: null,
    el: null,
    anchor: null,
    target: null,
    targetAnchor: null,
    shapeFlag,
    patchFlag,
    dynamicProps,
    dynamicChildren: null,
    appContext: null
  }

  // Check if the child node is valid
  normalizeChildren(vnode, children)

  // Check the child nodes of suspense
  if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
    // more ...
  }

  return vnode
}

It is worth mentioning that vue3 uses bitwise operations to enumerate element types. Personal understanding is: instead of using number nodeType, binary 0000, 0010, 0100, and 1000 are used to facilitate confirmation of node types. Just compare the current tag One of them can determine the node type (welcome to discuss in the comment area~)

  1. After getting the vnode, add the current instance context to the vnode
  2. Then judge whether fusion is required (No). Call the render method (the render method at this time is passed in through the formal parameters when creating the app instance, and the declaration is in the renderer)
  3. Set the mount status to true after render is complete

It can be seen that the mount method only wants to do two things

  1. create vnode
  2. render vnode

render

Rendering method entry: runtime-core -&gt; renderer -&gt; render

  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
  }

The render method wants to do two things

  1. patch vnode
  2. flushPostFlushCbs (later in this part)

Today, let's study the process of patch patching first.

// Remember the input parameters when calling patch
 patch(container._vnode || null, vnode, container, null, null, null, isSVG)

patch

Patching method: runtime-core -&gt; renderer -&gt; patch

const patch: PatchFn = (
    n1, // old VNODE
    n2, // new VNODE
    container, // mounted node
    anchor = null,
    parentComponent = null,
    parentSuspense = null,
    isSVG = false,
    slotScopeIds = null,
    optimized = false
  ) => {
    // if old node exists and n1 n2 are of different types
    // uninstall old vnode
    if (n1 && !isSameVNodeType(n1, n2)) {
      anchor = getNextHostNode(n1)
      unmount(n1, parentComponent, parentSuspense, true)
      n1 = null
    }

    // Get type , ref , shapeFlag from new vnode
    // first use type to match
    // If type does not match, use shapeFlag to match
    const { type, ref, shapeFlag } = n2
    // Currently the type in our vnode is the user's incoming configuration + innerHTML of the mounted container
    // So directly match the condition of shapeFlag
    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) {
          // match element
          processElement(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
          // match component
          // At this time shapeFlag will enter this condition
          processComponent(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
        } else if (shapeFlag & ShapeFlags.TELEPORT) {
          // match teleport
          // more
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
          // match suspense
          // more
        }
    }

    // handle ref
    if (ref != null && parentComponent) {
      setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)
    }
  }

It can be seen that the core goal of the patch method is to compare the new and old vnode nodes and perform different operations on them. When initializing the rendering, our root component will be patched first. Then we will go to the logical branch of processComponent and take a look at his specific implementation.

processComponent

process component : runtime-core -&gt; renderer -&gt; processComponent

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 old vnode is empty
  if (n1 == null) {
    // if the new vnode is KEPT_ALIVE
    if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
      // more
    } else {
      // mount node
      mountComponent(
        n2,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        optimized
      )
    }
  }
  // compare and update
  else {
    updateComponent(n1, n2, optimized)
  }
}

mountComponent

For initial rendering: runtime-core -&gt; renderer -&gt; mountComponent

const mountComponent: MountComponentFn = (
    initialVNode, // initialized vnode
    container, // mount target
    anchor,
    parentComponent,
    parentSuspense,
    isSVG,
    optimized
  ) => {
    // create component instance
    // createComponentInstance initializes a component instance model
    const compatMountInstance =
      __COMPAT__ && initialVNode.isCompatRoot && initialVNode.component
    const instance: ComponentInternalInstance =
      compatMountInstance ||
      (initialVNode.component = createComponentInstance(
        initialVNode,
        parentComponent,
        parentSuspense
      ))

    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!
    // handle prop slot and setup
    // Preliminary analysis
    if (!(__COMPAT__ && compatMountInstance)) {
      setupComponent(instance)
    }

    // post analysis
    setupRenderEffect(
      instance,
      initialVNode,
      container,
      anchor,
      parentSuspense,
      isSVG,
      optimized
    )
  }

setupComponent

Handling registered setup : runtime-core -&gt; component -&gt; setupComponent

Vue3 source code analysis 01-initialization rendering process

Slots and props are initialized in this method. After the end, it will first determine whether the component is stateful (according to the shapeFlag of the component)

Let's focus on the back, he tries to get the execution result of the setup, and starts parsing the setup by calling the setupStatefulComponent method

setupStatefulComponent

Start processing registered setup: runtime-core -&gt; component -&gt; setupStatefulComponent

function setupStatefulComponent(
  instance: ComponentInternalInstance,
  isSSR: boolean
) {
  // At this time instance.type === user registered setup + innerHTML under the container
  const Component = instance.type as ComponentOptions

  // 0. Add a cache object to the instance
  instance.accessCache = Object.create(null)
  // 1. Render the global proxy
  instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
  // 2. Call setup to get the return
  const { setup } = Component
  // Parse setup
  if (setup) {
    // Parse setup 中的参数
    // function.length is to get the number of parameters in the function
    // If the formal parameter quantity in setup &gt; 1 is used
    // create the setup context
    const setupContext = (instance.setupContext =
      setup.length > 1 ? createSetupContext(instance) : null)

    currentInstance = instance

    // Execute setup here to get the return value of setup execution
    // It is worth mentioning that
    // When setup is executed, our registered ref reactive will be executed at the same time
    // like in const count = ref(0)
    // The count we got is already data wrapped by ref
    const setupResult = callWithErrorHandling(
      setup,
      instance,
      ErrorCodes.SETUP_FUNCTION,
      [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
    )

    currentInstance = null

    // if return promise
    if (isPromise(setupResult)) {
      // Save the promise result to the outside through this function
      const unsetInstance = () => {
        currentInstance = null
      }
      setupResult.then(unsetInstance, unsetInstance)
    } else {
      // go to the result method
      handleSetupResult(instance, setupResult, isSSR)
    }
  } else {
    finishComponentSetup(instance, isSSR)
  }
}

handleSetupResult

Handling the execution result of setup: runtime-core -&gt; component -&gt; handleSetupResult

export function handleSetupResult(
  instance: ComponentInternalInstance,
  setupResult: unknown,
  isSSR: boolean
) {
  // The user-defined render function is returned in setup
  if (isFunction(setupResult)) {
    // if the current runtime is node
    if (__NODE_JS__ && (instance.type as ComponentOptions).__ssrInlineRender) {
      instance.ssrRender = setupResult
    } else {
      // Mount renderFunction on the instance
      instance.render = setupResult as InternalRenderFunction
    }
  }
  // composition api
  else if (isObject(setupResult)) {
    // Add the result to the instance's setupState
    instance.setupState = proxyRefs(setupResult)
  }
  // if no return value
  else if (__DEV__ && setupResult !== undefined) {
   // warn...
  }
  // end processing setup
  finishComponentSetup(instance, isSSR)
}

finishComponentSetup

Get render function &amp;&amp; compatible with vue2.x optionAPI : runtime-core -&gt; component -&gt; finishComponentSetup

export function finishComponentSetup(
  instance: ComponentInternalInstance,
  isSSR: boolean,
  skipOptions?: boolean
) {
  // Component = user registration configuration + innerHTML under the mount target
  const Component = instance.type as ComponentOptions

  // judgment runtime
  if (__NODE_JS__ && isSSR) {
    // more
  }
  // if the instance doesn't have a render function
  // which is
  // setup has no return function
  else if (!instance.render) {
    // Check if the user has not registered the render function elsewhere
    if (compile && !Component.render) {
      // get Component.template
      // which is
      // innerHTML under the container
      const template =
        (__COMPAT__ &&
          instance.vnode.props &&
          instance.vnode.props['inline-template']) ||
        Component.template

      if (template) {
        // ...more
        const finalCompilerOptions: CompilerOptions = extend(
         // ...more
        )
        // Execute the compile method (the compile method will be analyzed together when the compiler is subsequently analyzed)
        // Get the render function mounted on the component
        // The goal is to execute the compile method by
        // get the render function
        Component.render = compile(template, finalCompilerOptions)
      }
    }
    // After getting the render function
    // mount the render to the entire instance
    instance.render = (Component.render || NOOP) as InternalRenderFunction
  }

  // Begin to be compatible with 2.x API
  // So the 2.x API is compatible only after the setup parsing is completed
  // support for 2.x options
  if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
    currentInstance = instance
    applyOptions(instance)
    currentInstance = null
  }
}

In this method, we can get a very important information. The processing time of setup is very, very early in the life cycle. Why is vue3 compatible with the optionAPI of vue2, because it processes the setup first and then initializes it Other optionAPI. Because the calling time is early, it can be backward compatible (seconds~)

The compatible process and the compilation process will be sorted together later. Understand the overall operation process first, and then deduct the details.

When we finish executing this method, we return to the mountComponent method. He executes the setupRenderEffect

setupRenderEffect

Core methods, dependency collection, initial rendering, and subsequent updates are all performed through this method. : runtime-core -&gt; renderer -&gt; setupRenderEffect

const setupRenderEffect: SetupRenderEffectFn = (
    instance,
    initialVNode,
    container,
    anchor,
    parentSuspense,
    isSVG,
    optimized
  ) => {
    // add update method
    // The value is the execution result of the effect
    // effect is uniformly analyzed when the reactive package (reactive system) is subsequently analyzed
    // For the time being, we understand that he will receive a notification to execute the incoming componentEffect method when initializing the rendering
    instance.update = effect(function componentEffect() {
      // initialize rendering
      if (!instance.isMounted) {
        // ssr related
        if (el && hydrateNode) {
          // more ...
        } else {
          // !!!!!!!!!!!!!!!!
          // build subTree
          // subTree is Vnode
          // Gather dependencies at this point (discussed later)
          const subTree = (instance.subTree = renderComponentRoot(instance))
          // !!!!!!!!!!!!!!!!
          // Patch the subTree
          patch(
            null,
            subTree,
            container,
            anchor,
            instance,
            parentSuspense,
            isSVG
          )
        }
        // change the mount state
        instance.isMounted = true
      }
      // update subsequent analysis
      else {
        // more ...
      }
    }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
  }

We can see that this method wants to build a subTree, and then patch the subTree. Let's analyze the process of building a subTree.

renderComponentRoot

构建 subTree : runtime-core -> componentRenderUtils -> renderComponentRoot

export function renderComponentRoot(
  instance: ComponentInternalInstance
): VNode {
  const {
    type: Component,
    vnode,
    proxy,
    withProxy,
    props,
    propsOptions: [propsOptions],
    slots,
    attrs,
    emit,
    render, // render function obtained by compile
    renderCache,
    data,
    setupState,
    ctx,
    inheritAttrs
  } = instance

  let result

  try {
    let fallthroughAttrs
    if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
      // here is called to call render first
      // After the call, we will get the vnode tree of the node under the container
      // Then check whether the vnode tree is a valid vnode
      // If you get a valid vnode, assign it to result
      // Dependency collection done here
      result = normalizeVNode(
        render!.call(
          proxyToUse,
          proxyToUse!,
          renderCache,
          props,
          setupState,
          data,
          ctx
        )
      )
      fallthroughAttrs = attrs
    } else {
      // if it is a stateless component
      // more ...
    }

    let root = result
    let setRoot: ((root: VNode) => void) | undefined = undefined

    if (vnode.dirs) {
      // merge command
      root.dirs = root.dirs ? root.dirs.concat(vnode.dirs) : vnode.dirs
    }
    if (vnode.transition) {
      // inherit transition
      root.transition = vnode.transition
    }

    if (__DEV__ && setRoot) {
      setRoot(root)
    } else {
      result = root
    }
  } catch (err) {
    blockStack.length = 0
    handleError(err, instance, ErrorCodes.RENDER_FUNCTION)
    result = createVNode(Comment)
  }

  return result
}

You can see that this method turns all the nodes under our container into vnodes. After verifying the legitimacy of vnodes, some properties will be merged and inherited. Finally, vnodes will be returned.

Let's go back to the setupRenderEffect method. The meaning of subTree is to mount the vnodeTree of the container. We need to patch this vnodeTree.

We're back to the patch method again.

const patch: PatchFn = (
    n1, // null
    n2, // subTree
    container, // mounted node
    anchor = null,
    parentComponent = null,
    parentSuspense = null,
    isSVG = false,
    slotScopeIds = null,
    optimized = false
  ) => {
    // if old node exists and n1 n2 are of different types
    // uninstall old vnode
    if (n1 && !isSameVNodeType(n1, n2)) {
      anchor = getNextHostNode(n1)
      unmount(n1, parentComponent, parentSuspense, true)
      n1 = null
    }
    // at this time
    // type is the child element type. In our example, the type is div
    // shapeFlag is still the component type flag
    const { type, ref, shapeFlag } = n2
    switch (type) {
      case Text:
        // more ...
        break
      case Comment:
        // more ...
        break
      case Static:
        // more ...
        break
      case Fragment:
        // more ...
        break
      default:
        if (shapeFlag & ShapeFlags.ELEMENT) {
          // at this time我们会进入这个条件
          processElement(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
          // more
        } else if (shapeFlag & ShapeFlags.TELEPORT) {
          // match teleport
          // more
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
          // match suspense
          // more
        }
    }

    // handle ref
    if (ref != null && parentComponent) {
      setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)
    }
  }

processElement

处理 element : runtime-core -> renderer -> processElement

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'
    // The old node does not exist directly mounted
    if (n1 == null) {
      mountElement(
        n2,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    }
    // there is a comparison update
    else {
      patchElement(
        n1,
        n2,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    }

mountElement

Handle child element insertion into target node: runtime-core -&gt; renderer -&gt; mountElement

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
    // go here!
    // Call hostCreateElement to create the real DOM
    el = vnode.el = hostCreateElement(
      vnode.type as string,
      isSVG,
      props && props.is,
      props
    )
    // if the child node is text
    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
      // Manipulate dom to assign text
      hostSetElementText(el, vnode.children as string)
    }
    // if the child node is an array
    else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
      // mount child nodes
      mountChildren(
        vnode.children as VNodeArrayChildren,
        el, // !!!!
        null,
        parentComponent,
        parentSuspense,
        isSVG && type !== 'foreignObject',
        slotScopeIds,
        optimized || !!vnode.dynamicChildren
      )
    }

    // if the current element has props
    if (props) {
      // loop through props
      for (const key in props) {
        if (!isReservedProp(key)) {
          // apply attribute patch to the element
          hostPatchProp(
            el,
            key,
            null,
            props[key],
            isSVG,
            vnode.children as VNode[],
            parentComponent,
            parentSuspense,
            unmountChildren
          )
        }
      }
    }
    // Insert the current operation node into the corresponding container
    hostInsert(el, container, anchor)
  }

mountChildren

Mount child nodes: runtime-core -&gt; renderer -&gt; mountChildren

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

Here we can see a detail. Every time he mounts, the mounted container is the real DOM generated according to the type of the parent node. After the mountChildren is finished, he will insert the corresponding node under the parent element. Because It is recursive, so the node analyzed first is mounted. When the outermost node is mounted, all nodes are mounted (seconds~)

At this point, the process of initializing rendering is roughly over.

Summarize the initialization rendering process

Create renderer –&gt; create app instance –&gt; call mount method –&gt; create vnode (based on the option passed in during initialization) –&gt; call render –&gt; patch –&gt; processComponent –&gt; mountComponent –&gt; call setup gets its result -&gt; call compile to get the render function of innerHTML under the container -&gt; API compatible with vue2.x –&gt; setupRenderEffect –&gt; build subTree –&gt; processElement –&gt; mountElement –&gt; recursively patch child elements — &gt; Add props –&gt; Insert under the corresponding parent node

In general, this process is still relatively complicated. If there is any dispute in the process, please point it out in the comment area. I will update the article in time. At the same time, it is not easy to code words.

Additional analysis is currently required

  1. compile
  2. Backward Compatibility Details
  3. reactive
  4. update related

Recommended Today

Fourth, handwritten SpringMVC framework, business layer – what is coupling/dependency? How to solve

4. Business Layer 4.1 MVC model MVC:Model(Model),View(view),Controller(controller) View layer: an interface for displaying data and interacting with users=>jsp Control layer: It can accept the client's request and forward the request. The specific business function still needs to be completed with the help of the model layer component. CoreServlet  => DispacherServlet + EmpController Model layer: There are […]