“This is the 18th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021”

Create a virtual node. Create a virtual node. Create a virtual node. Note that this article focuses on the process of mounting new components, and we will continue with component updates in the next article.

Attach application context mount source code: PS: article catalog sequence and execution process.

/ /... Other code omitted mount(rootContainer: HostElement, isHydrate? : boolean): any { if (! IsMounted) {// create a virtual node based on the rootComponent const vnode = createVNode(rootComponent as Component, rootProps) vnode.appContext = context // HMR root reload if (__DEV__) { context.reload = () => { Render (cloneVNode(vnode), rootContainer)}} rootContainer) isMounted = true app._container = rootContainer // for devtools and telemetry ; (rootContainer as any).__vue_app__ = app return vnode.component! .proxy } },Copy the code

If you are not familiar with the previous content, it is recommended to familiarize yourself with the process, the content of the article is as follows:

Vue3 source | createApp are done?

createVNode

This method generates a VNode virtual node based on the component and component properties.

What is a virtual node and what are its benefits?

The essence of a VNode is a JavaScript object that describes the DOM, a description of something abstract.

  1. cross-platform
  2. Provides a medium for data-driven views
  3. For scenarios where the DOM is frequently manipulated through JavaScript, VNode performs better because it waits for enough changes to be collected before applying them to the real DOM all at once.

Here is the source code to generate VNode:

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 { if (! Type | | type = = = NULL_DYNAMIC_COMPONENT) {/ / don't preach, the default Comment type of virtual node type = Comment} / / is already a virtual node, the clone a, Return if (isVNode(type)) {const verification = cloneVNode(type, props) if (children) {normalizeChildren(verification, Children)} return standardization of class components. If (isFunction(type) && '__vccOpts' in type) {type = type.__vccopts} // style and class standardization. if (props) { // for reactive or proxy objects, we need to clone it to enable mutation. if (isProxy(props) || InternalObjectKey in props) { props = extend({}, props) } let { class: klass, style } = props if (klass && ! isString(klass)) { props.class = normalizeClass(klass) } if (isObject(style)) { // reactive state objects need to be cloned since they are likely to be // mutated if (isProxy(style) && ! isArray(style)) { style = extend({}, Style)} props. Style = normalizeStyle(style)}} 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 // Create a virtual node const vnode: vnode = {__v_isVNode: true, __v_skip: true, type, props, key: props && normalizeKey(props), ref: props && normalizeRef(props), scopeId: currentScopeId, children: null, el: null, staticCount: 0, shapeFlag, patchFlag, dynamicProps, ... NormalizeChildren (vNode, children); normalizeChildren(vNode, children); return vnode }Copy the code

From code parsing, we can see that createVNode does several things:

  1. Standardize the properties props
  2. Encode VNode type information as a bitmap
  3. Create a VNode object
  4. Normalize the child nodes

render

VNode exists, let’s see how to render.

const render: RootRenderFunction = (vnode, container) => {if (vnode == null) {// The virtual node is null, If (container._vnode) {unmount(container._vnode, null, null, true)}} else {// Create or update a node, Container._vnode does not exist when it is created. Old virtual node // Second argument: new vnode // third argument: Vnodes are converted to dom, Final patch to mount the dom container (container. _vnode | | null, vnode, container)} flushPostFlushCbs () container. _vnode = vnode}Copy the code

The Render function is very simple and decides whether to destroy, create, or update the component based on the argument passed. Let’s look at creating a process.

patch

Contains implementations of component creation, update, and update. Let’s first look at the creation logic.

const patch: PatchFn = (n1, old node n2, // new node container, // DOM container, Anchor = NULL, parentComponent = null, parentSuspense = NULL, isSVG = false, Optimized = false) => {// Old nodes exist, and the new node is different, uninstall the old node if (n1 &&! isSameVNodeType(n1, n2)) { anchor = getNextHostNode(n1) unmount(n1, parentComponent, parentSuspense, } // PatchFlags.bail: A special sign, If (n2.patchFlag === PatchFlags.bail) {optimized = false n2.dynamicChildren = null} const {type, the Differ algorithm should exit optimization mode. Ref, shapeFlag} = n2 switch (type) {case Text: break case Comment: break case Static: Break case Fragment: // Break case Fragment: // Handle the DOM ELEMENT if (shapeFlag & shapeFlags.Element) {processElement(n1, n2, container, anchor, parentComponent, ParentSuspense, isSVG, Optimized)} else if (shapeFlag & ShapeFlags.COMPONENT) { container, anchor, parentComponent, parentSuspense, isSVG, Optimized)} else if (shapeFlag & shapeflags.teleport) {// TELEPORT} else if (__FEATURE_SUSPENSE__ && shapeFlag & SUSPENSE}} {SUSPENSE}} {SUSPENSE}} = null && parentComponent) { setRef(ref, n1 && n1.ref, parentComponent, parentSuspense, n2) } }Copy the code

From the source can be seen, when the old and new virtual nodes are different, will uninstall the old node first. In addition, there are eight different types of Vnodes. In the patch function, corresponding processing will be performed according to the type of vNode, such as mounting DOM or updating DOM.

Let’s take a look at how components and DOM elements are typically handled.

processElement

See how to mount DOM elements.

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' / / the old node does not exist, then mount the new elements, Otherwise update if (n1 == null) {mountElement(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) } else { patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized) } }Copy the code

This article only looks at the mountElement process, the following look at the mountElement method, because there is a lot of function code, below truncated, only focus on the main process.

const mountElement = ( vnode: VNode, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, optimized: boolean ) => { let el: RendererElement let vnodeHook: VNodeHook | undefined | null const { type, props, shapeFlag, transition, scopeId, patchFlag, Dirs} = vnode // create dom element node if (! __DEV__ && vnode.el && hostCloneNode ! == undefined && PatchFlags.HOISTED) {// vnode. el is not empty, indicating that it is to be reused. Only static Vnodes can be reused. El = vnode.el = hostCloneNode(vnode.el)} else {// 1. Create a new element el = vnode.el = hostCreateElement(vnode.type as string, isSVG, props && props. First mount the child node, If (shapeFlags.text_children) {if (shapeFlags.text_children) {hostSetElementText(el, Vnode. children as string)} else if (ShapeFlags & shapeflags.array_children) {mountChildren( vnode.children as VNodeArrayChildren, el, null, parentComponent, parentSuspense, isSVG && type ! == 'foreignObject', optimized || !! // If (props) {for (const key in props) {if (! isReservedProp(key)) { hostPatchProp( el, key, null, props[key], isSVG, vnode.children as VNode[], parentComponent, parentSuspense, UnmountChildren)}} if ((vnodeHook = props. OnVnodeBeforeMount)) {invokeVNodeHook(vnodeHook, parentComponent, vnode) } } // ... HostInsert (el, container, anchor)}Copy the code

The whole process can be traced as follows:

  1. Create a DOM element ifvNode.elIf the node is not empty and is a static virtual node, clone one.
  2. The element child nodes are mounted first because the current node may depend on the attributes of the child node. If the child node is a text node, the node content is set directly. If the node is an array, the child node is traversed, and the patch operation is performed recursively. Relevant code, you can view.
  3. If the attribute exists, the associated attribute of the element is processed.
  4. Mount elements to containerscontainerOn.
processComponent

Here we look directly at the logic of component mounting.

const mountComponent: MountComponentFn = ( initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) => { // 1. Create a component instance const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance( initialVNode, parentComponent, ParentSuspense) // Inject keepAlive into the renderer if (isKeepAlive(initialVNode)) {; (instance.ctx as KeepAliveContext).renderer = internals} // Set the component instance setupComponent(instance) // Set and execute the rendering function with side effects setupRenderEffect( instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized ) }Copy the code

You can see that the function performs the following steps:

  1. Creating a component instance
  2. Setting up component instances
  3. Execute render function with side effects

Let’s see how the render function with side effects is executed.

const setupRenderEffect: SetupRenderEffectFn = ( instance, initialVNode, container, anchor, parentSuspense, isSVG, Optimized) => {instance.update = effect(function componentEffect() {if (! instance.isMounted) { let vnodeHook: VNodeHook | null | undefined const { el, props } = initialVNode const { bm, m, a, Parent} = instance // Component instance generates subTree vnode const subTree = (instance.subTree = renderComponentRoot(instance)) // beforeMount hook if (bm) { invokeArrayFns(bm) } // onVnodeBeforeMount if ((vnodeHook = props && props.onVnodeBeforeMount)) { invokeVNodeHook(vnodeHook, parent, InitialVNode)} if (el && hydrateNode) {// omit} else {// Mount subTree to container patch(null, subTree, container, anchor, instance, parentSuspense, // Mounted hook if (m) {queuePostRenderEffect(m, queuePostRenderEffect(m, queuePostRenderEffect(m, queuePostRenderEffect(m, queuePostRenderEffect(m, queuePostRenderEffect)); parentSuspense) } // onVnodeMounted if ((vnodeHook = props && props.onVnodeMounted)) { queuePostRenderEffect(() => { invokeVNodeHook(vnodeHook! , parent, initialVNode) }, ParentSuspense)} // Activate hooks for keep-alive nodes if (a && InitialVNode.shapeflags.com PONENT_SHOULD_KEEP_ALIVE) { QueuePostRenderEffect (a, parentSuspense)} instance.isMounted = true} else {// Update components-related logic}}, prodEffectOptions)}Copy the code

From the source can be handled as follows:

  1. Effect creates a side effect rendering functioncomponentEffectWhen the component data changes, the function is re-executed.
  2. The rendering component generates subtreessubTreeAnd mount the subtree to
  3. Saves the root node of the subtree to the current node
  4. The entire component mount process executes some hook functions such asBeforeMount, Mount, as well askeep-aliveProcessing.

conclusion

At this point you have read the component mount process. The virtual node VNode is created first, and then the render logic is performed. If the created VNode is null, the component performs the uninstall process; otherwise, the create or update process is performed. This article explains the mounting process. There are eight types of VNodes to create,

We analyzed components and elements. To mount an element, create a DOM element, update the attributes of the element, and recursively mount child nodes. For details about DOM operations, refer to nodeOps. To mount components, create component instances -> Set component instances -> execute rendering functions with side effects, render component subtrees, and read the ComponentRenderUtils.ts file for detailed implementation of component rendering.

The relevant codes are as follows:

packages/runtime-dom/src/index.ts

Packages/Runtime-core/SRC /vnode.ts // Virtual nodes

Packages/Run-time core/ SRC /renderer.ts //

Packages/runtime – core/SRC/componentRenderUtils ts / / component rendering methods

Packages/Runtime-dom/SRC/nodeops. ts // Describes node operations