createApp

Source location: github.com/vuejs/vue-n…

Are source code, more dry… V 3.0.4

Code implementation:

/** * createApp function */
export const createApp = ((. args) = > {
  constapp = ensureRenderer().createApp(... args)// The development environment verifies that the component's name is not the same as the built-in label
  if (__DEV__) {
    injectNativeTagCheck(app)
  }

  const { mount } = app
  /** * overrides the mount function *@param containerOrSelector 
   */
  app.mount = (containerOrSelector: Element | ShadowRoot | string) :any= > {
    // Container is a real DOM element
    const container = normalizeContainer(containerOrSelector)
    if(! container)return
    const component = app._component // Options for the component
    // The template of the default component is the contents of the mount element
    if(! isFunction(component) && ! component.render && ! component.template) { component.template = container.innerHTML }// Empty the contents of the container
    container.innerHTML = ' '
    const proxy = mount(container)
    if (container instanceof Element) {
      // Remove the V-cloak instruction on the element
      container.removeAttribute('v-cloak')
      container.setAttribute('data-v-app'.' ')}return proxy
  }

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

Call ensureRenderer (.) createApp (… Args) method to obtain the instance of app; Then we rewrite the app’s mount method. In the new mount method, we do the first thing on the container (if the CSS selector is passed, we get the DOM element from document.querySelector). Make container a real DOM element.

When the component is not a function and the render function and tempalte parameters are not set, the innerHTML in the default Container is the template for the component.

Call the mount method returned in app to complete the DOM mount

ensureRenderer

/** * Lazy create renderer */
function ensureRenderer() {
  return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}
Copy the code

The renderer object is lazily created, depending on the platform on which the object is created. In the WEB platform, rendererOptions is an API for DOM manipulation

createRenderer

export function createRenderer<
  HostNode = RendererNode.HostElement = RendererElement> (options: RendererOptions<HostNode, HostElement>) {
  return baseCreateRenderer<HostNode, HostElement>(options)
}
Copy the code

This method directly returns the baseCreateRenderer method, which has several overloaded methods.

baseCreateRenderer

Source location: github.com/vuejs/vue-n…

function baseCreateRenderer(options: RendererOptions, createHydrationFns? :typeof createHydrationFunctions
) :any {
  // The WEB platform gets the method to manipulate the DOM
  const {
    insert: hostInsert,
    remove: hostRemove,
    patchProp: hostPatchProp,
    forcePatchProp: hostForcePatchProp,
    createElement: hostCreateElement,
    createText: hostCreateText,
    createComment: hostCreateComment,
    setText: hostSetText,
    setElementText: hostSetElementText,
    parentNode: hostParentNode,
    nextSibling: hostNextSibling,
    setScopeId: hostSetScopeId = NOOP,
    cloneNode: hostCloneNode,
    insertStaticContent: hostInsertStaticContent
  } = options
  const patch = () = >{... }const processText = () = >{... }const processCommentNode = () = >{... }const mountStaticNode = () = >{... }const patchStaticNode = () = >{... }const moveStaticNode = () = >{... }const removeStaticNode = () = >{... }const processElement = () = >{... }const mountElement = () = >{... }const setScopeId = () = >{... }const mountChildren = () = >{... }const patchElement = () = >{... }const patchBlockChildren = () = >{... }const patchProps = () = >{... }const processFragment = () = >{... }const processComponent = () = >{... }const mountComponent = () = >{... }const updateComponent = () = >{... }const setupRenderEffect = () = >{... }const updateComponentPreRender = () = >{... }const patchChildren = () = >{... }const patchUnkeyedChildren = () = >{... }const patchKeyedChildren = () = >{... }const move = () = >{... }const unmount = () = >{... }const remove = () = >{... }const removeFragment = () = >{... }const unmountComponent = () = >{... }const unmountChildren = () = >{... }const getNextHostNode = () = >{... }const render = () = >{... }return {
    render,
    hydrate,
    / / createApp entrance
    createApp: createAppAPI(render, hydrate)
  }
}
Copy the code

EnsureRenderer ().createApp(… The args method gets an instance of app, which is createApp in the object returned by baseCreateRenderer, a function generated by createAppAPI

createAppAPI

Source location: github.com/vuejs/vue-n…

/** * returns app instance *@param render 
 * @param hydrate 
 */
export function createAppAPI<HostElement> (render: RootRenderFunction, hydrate? : RootHydrateFunction) :CreateAppFunction<HostElement> {
  /** * receives two arguments * rootComponent rootComponent * rootProps passed to the rootComponent props */
  return function createApp(rootComponent, rootProps = null) {
    const context = createAppContext() // Return an object
    // The installed plug-in
    const installedPlugins = new Set(a)// Whether to mount
    let isMounted = false
    
    const app = (context.app = {
        _uid: uid++, / / the only id
        _component: rootComponent as ConcreteComponent,
        _props: rootProps,
        _container: null._context: context,

        version, / / the vue version

        get config() { // config is a read-only object. Setting config will raise a warning in the development environment
            return context.config
        },

        use(){... },mixin(){... },component(){... },directive(){... },mount(){... },unmount(){... },provide() {...}
    })
    
    return app
  }
}
Copy the code

In createApp of the Runtime-dom, override the mount method to call app.mount.

mount

/** * Components mount *@param rootContainer 
   * @param isHydrate 
   */mount(rootContainer: HostElement, isHydrate? :boolean) :any {
    if(! isMounted) {// Create a vnode
        const vnode = createVNode(
            rootComponent as ConcreteComponent,
            rootProps
        )
        // The vnode of the node mounts the context
        vnode.appContext = context
        // Ignore execution on other platforms, and warnings from development environments
        // ...
        // Execute the render function
        render(vnode, rootContainer)
        isMounted = true
        app._container = rootContainer
        / / return???
        returnvnode.component! .proxy } }Copy the code

In the mount method, the createVNode method is executed to create a component’s VNode, followed by the Render method.

createVNode

Example Create a Vnode

export const createVNode = (__DEV__
  ? createVNodeWithArgsTransform
  : _createVNode) as typeof _createVNode
Copy the code

Regardless of your development environment, look directly at the _createVNode method

_createVNode

/** * How to create a vNode *@param type 
 * @param props 
 * @param children 
 * @param patchFlag 
 * @param dynamicProps 
 * @param isBlockNode 
 */
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 (isVNode(type)) {
    // If type is already a vnode, return the clone vnode
    const cloned = cloneVNode(type, props, true /* mergeRef: true */)
    if (children) {
      normalizeChildren(cloned, children)
    }
    return cloned
  }
  // Handle the class component. The class component has been removed from Vue3
  // ...
  // class & style normalization.
  // Handle props class as a string, style as an object
  
  // encode the vnode type information into a bitmap
  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
  // A new vNode
  const vnode: VNode = {
    __v_isVNode: true,
    [ReactiveFlags.SKIP]: true.type,
    props,
    key: props && normalizeKey(props),
    ref: props && normalizeRef(props),
    scopeId: currentScopeId,
    children: null.component: null.suspense: null.ssContent: null.ssFallback: null.dirs: null.transition: null.el: null.anchor: null.target: null.targetAnchor: null.staticCount: 0,
    shapeFlag,
    patchFlag,
    dynamicProps,
    dynamicChildren: null.appContext: null
  }

  // validate key The validate key is not a NaN
  // ...
  // Process the child nodes
  normalizeChildren(vnode, children)
  if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
    const { content, fallback } = normalizeSuspenseChildren(vnode)
    vnode.ssContent = content
    vnode.ssFallback = fallback
  }

  if (
    shouldTrack > 0! isBlockNode && currentBlock && (patchFlag >0|| shapeFlag & ShapeFlags.COMPONENT) && patchFlag ! == PatchFlags.HYDRATE_EVENTS ) { currentBlock.push(vnode) }return vnode
}
Copy the code

This method returns a new vNode, even if the argument passed is already a vnode, clone a new vnode and return.

Then look at render, the other method called in mount

render

The render method called in mount is passed in when the createAppAPI method is called. This is the Render method defined in the baseCreateRenderer method

const render: RootRenderFunction = (vnode, container) = > {
    // vnode === null Uninstalls components
    if (vnode == null) {
        if (container._vnode) {
            // Uninstalling components needs to be performed
            unmount(container._vnode, null.null.true)}}else {
        // Patch updates and/or creates components
        patch(container._vnode || null, vnode, container)
    }
    flushPostFlushCbs()
    // Save the vNode of the component
    container._vnode = vnode
}
Copy the code

Render method through the patch method, vNode into a real DOM, and mount on the page.

patch

  const patch: PatchFn = (
    n1,
    n2,
    container,
    anchor = null,
    parentComponent = null,
    parentSuspense = null,
    isSVG = false,
    optimized = false
  ) = > {
    // Delete the old node if the new node type is different
    if(n1 && ! isSameVNodeType(n1, n2)) { anchor = getNextHostNode(n1) unmount(n1, parentComponent, parentSuspense,true)
      n1 = null
    }
    const { type, ref, shapeFlag } = n2
    switch (type) {
      case Text:
        // ...
        break
      case Comment:
        // ...
        break
      case Static:
        // ...
        break
      case Fragment:
        // ...
        break
      default:
        // Handle other types of nodes
        // ...
        if (shapeFlag & ShapeFlags.COMPONENT) {
          processComponent( / / processing component
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            optimized
          )
        }
    }
    // Handle the bound ref
    if(ref ! =null && parentComponent) {
      setRef(ref, n1 && n1.ref, parentSuspense, n2)
    }
  }
Copy the code

During patch, different methods are called to process the node depending on the type of the vNode. Here we will focus on processComponent, which performs setup and dependency collection.

processComponent

const processComponent = (
    n1: VNode | null,
    n2: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    optimized: boolean
  ) = > {
    if (n1 == null) {
        // ...
      	/ / mount component
        mountComponent(
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
    } else {
      // Update the component
      updateComponent(n1, n2, optimized)
    }
  }
Copy the code

You can see that the mountComponent method is used to create the component and the updateComponent method is used to update the component. See mountComponent first and then updateComponent.

mountComponent

const mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) = > {
    // createComponentInstance handles instance. CTX inconsistencies are handled in this method
    const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
      initialVNode,
      parentComponent,
      parentSuspense
    ))
	// ...
    // The setup method is called, and the value returned by setup is stored in instance.setupState
    setupComponent(instance)
    // ...
    if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
      The setupRenderEffect function is executed after the promise state resolve
      parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect)
	  // ...
      return
    }

    setupRenderEffect(
      instance,
      initialVNode,
      container,
      anchor,
      parentSuspense,
      isSVG,
      optimized
    )
  }
Copy the code

This method creates an instance by createComponentInstance

This instance is obtained by getCurrentInstance. The CTX property of instance is two different things in dev and PROD, and should not be used in production

The setupRenderEffect function finally executes the setupRenderEffect method, in which the dependencies used in vNode are collected

createComponentInstance
export function createComponentInstance(
  vnode: VNode,
  parent: ComponentInternalInstance | null,
  suspense: SuspenseBoundary | null
) {
  // ...
  const instance: ComponentInternalInstance = {
    // ...
  }
  // Special processing of CTX by the development environment
  // This CTX cannot be used in project development, production environment does not support it
  if (__DEV__) {
    instance.ctx = createRenderContext(instance)
  } else {
    instance.ctx = { _: instance }
  }
  instance.root = parent ? parent.root : instance
  instance.emit = emit.bind(null, instance)

  return instance
}
Copy the code

setupRenderEffect

  const setupRenderEffect: SetupRenderEffectFn = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) = > {
    instance.update = effect(function componentEffect() {
      // Create a new component
      if(! instance.isMounted) {let vnodeHook: VNodeHook | null | undefined
        const { el, props } = initialVNode
        // For child nodes, the component's render function is executed
        // The render function collects the value of ref/reactive as the function to be triggered by the dependency change
        const subTree = (instance.subTree = renderComponentRoot(instance))
        patch( // The patch execution completes the DOM mount
            null,
            subTree,
            container,
            anchor,
            instance,
            parentSuspense,
            isSVG
          )
          initialVNode.el = subTree.el
        instance.isMounted = true
        initialVNode = container = anchor = null as any
      } else {
        // Update the component
        let { next, bu, u, parent, vnode } = instance
        let originNext = next
        let vnodeHook: VNodeHook | null | undefined
        const nextTree = renderComponentRoot(instance)
        const prevTree = instance.subTree
        instance.subTree = nextTree
        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
        )
        next.el = nextTree.el
      }
    }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
  }
Copy the code

The setupRenderEffect function calls the effect function. Only functions executed in effect can do dependency collection. Create child nodes of the component using renderComponentRoot, which executes the render method of the component. The effect function is collected as a dependency on changes in the Render method for getting values of type Reactive and for ref/computed. Value.

When effect is executed, the second argument prodEffectOptions is passed. In this argument, a scheduler method is called after the dependency update. This scheduler determines when the DOM update will be performed. Instead of changing the DOM every time a dependency changes.

This method also executes the beforeMount hooks function, then executes a patch method after renderComponentRoot, in which the component is created into the DOM. And handles the ref bound in the component template. Through the setRef method.

Mounted hooks are then executed.

Component rendering is finished!!