Vue 3.2.26 source code interpretation (a) Reactivity responsive vUE 3.2.26 source code interpretation (two) initialize render Vue (three) Diff algorithm principle

We in the previous vUE 3.2.26 source code interpretation (a) reactivity responsivity introduced vue3 responsivity principle, on this basis we step by step to analyze how data is mapped to the view.

demo

We’ll write a demo first, and since the Compiler part is not covered in this article, we’ll initialize the page directly using a rendering function.

const {reactive, h} = Vue;
Vue.createApp({
    setup() {
        const a = reactive({arr: [1.2.3.4.5.6.7]});
        return () = > {
            const  lis = a.arr.map((item) = >{
                return h('li', {
                    key: item
                }, item)
            });
            return h('ul', {
                onclick: () = >{
                    a.arr = [1.6.2.4.3.5.7]
                }
            }, lis)
        }
    }
}).mount('#demo')
Copy the code

createApp

  1. Creating an App object
  2. Preserve original mount
  3. Rewrite the mount
export const createApp = ((. args) = > {
  constapp = ensureRenderer().createApp(... args)const { mount } = app
  app.mount = (containerOrSelector: Element | ShadowRoot | string) :any= > {
    / /...
  }

  return app
}) as CreateAppFunction<Element>
Copy the code

Rewritten mount

  1. Get container DOM
  2. Store the template template
  3. Empty container
  4. Perform original mount
  5. Delete special attributes
app.mount = (containerOrSelector: Element | ShadowRoot | string) :any= > {
    const container = normalizeContainer(containerOrSelector)
    if(! container)return

    const component = app._component
    if(! isFunction(component) && ! component.render && ! component.template) { component.template = container.innerHTM }// clear content before mounting
    container.innerHTML = ' '
    const proxy = mount(container, false, container instanceof SVGElement)
    if (container instanceof Element) {
      container.removeAttribute('v-cloak')
      container.setAttribute('data-v-app'.' ')}return proxy
}
Copy the code

Original mount method

  1. Create a root vnode
  2. Perform render
  3. Execute the patch method in Render and save the generated vNodes in the _vNode property on the container
mount( rootContainer: HostElement, isHydrate? :boolean, isSVG? :boolean) :any {
    if(! isMounted) {const vnode = createVNode(
        rootComponent as ConcreteComponent,
        rootProps
      )
      // store app context on the root VNode.
      // this will be set on the root instance on initial mount.
      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
      // for devtools and telemetry; (rootContaineras any).__vue_app__ = app

      returngetExposeProxy(vnode.component!) || vnode.component! .proxy } }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
  }
Copy the code

patch

The corresponding method is executed through the Type, shapeFlag properties, and processComponent is called

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) {
        processElement(
          n1,
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized
        )
      } else if (shapeFlag & ShapeFlags.COMPONENT) {
        processComponent(
          n1,
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized
        )
      } else if(shapeFlag & ShapeFlags.TELEPORT) { ; (type as typeof TeleportImpl).process(
          n1 as TeleportVNode,
          n2 as TeleportVNode,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized,
          internals
        )
      } else if(__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { ; (type as typeof SuspenseImpl).process(
          n1,
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized,
          internals
        )
      } else if (__DEV__) {
        warn('Invalid VNode type:'.type.` (The ${typeof type}) `)}}// set ref
  if(ref ! =null&& parentComponent) { setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, ! n2) } }Copy the code

processComponent

Determine whether old nodes exist and perform updates or mount

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

mountComponent

  1. Initialize the component column
  2. Call setupStatefulComponent in setupComponent, execute setup to get the return value and assign it to Render
  3. Perform setupRenderEffect
const mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) = > {
    // 2.x compat may pre-create the component instance before actually
    // mounting
    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)) { setupComponent(instance) } setupRenderEffect( instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized ) }Copy the code

setupRenderEffect

  1. Define componentUpdateFn
  2. Create the component Effect and pass in the Scheduler and Scope
  3. Bind run in Effect to update, specify internal this as the current effect, and execute update
  4. According to the reactive principle in the previous article, we know that the activeEffect is executed in component update n and is the current effect
const setupRenderEffect: SetupRenderEffectFn = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) = > {
  const componentUpdateFn = () = > {
    / /...
  }

  // create reactive effect for rendering
  const effect = (instance.effect = new ReactiveEffect(
    componentUpdateFn,
    () = > queueJob(instance.update),
    instance.scope // track it in component's effect scope
  ))

  const update = (instance.update = effect.run.bind(effect) as SchedulerJob)
  update.id = instance.uid
  // allowRecurse
  // #1801, #2043 component render effects should allow recursive updates
  toggleRecurse(instance, true)

  update()
}

Copy the code

componentUpdateFn

  1. Determine whether to update or add (since initialization logic only includes additions, we focus on additions)
  2. RenderComponentRoot is called to generate child VNodes (here the render function is called to trigger get dependency collection)
  3. When patch is called, the internal processElement is called
  4. Update the el and isMounted attributes
const componentUpdateFn = () = > {
  if(! instance.isMounted) {let vnodeHook: VNodeHook | null | undefined
    const { el, props } = initialVNode
    const { bm, m, parent } = instance
    const isAsyncWrapperVNode = isAsyncWrapper(initialVNode)

    / /...
    const subTree = (instance.subTree = renderComponentRoot(instance))
    
    patch(
      null,
      subTree,
      container,
      anchor,
      instance,
      parentSuspense,
      isSVG
    )
      
    initialVNode.el = subTree.el

    instance.isMounted = true
    
    initialVNode = container = anchor = null as any
  }else{
    / /...}}Copy the code

processElement

To determine if this is the first similar processComponent, we only look at mountElement

mountElement

  1. Create your own DOM
  2. Check whether the child node is a text node. If it is a text node, create it directly. Otherwise, call patch in mountChildren to form recursion
  3. Renders the property to the current DOM
  4. Inserts the current element into the parent node
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
  
  el = vnode.el = hostCreateElement(
    vnode.type as string,
    isSVG,
    props && props.is,
    props
  )

  // mount children first, since some props may rely on child content
  // being already rendered, e.g. `<select value>`
  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
    )
  }

  // props
  if (props) {
    for (const key in props) {
      if(key ! = ='value' && !isReservedProp(key)) {
        hostPatchProp(
          el,
          key,
          null,
          props[key],
          isSVG,
          vnode.children as VNode[],
          parentComponent,
          parentSuspense,
          unmountChildren
        )
      }
    }
    
    hostInsert(el, container, anchor)
}
Copy the code