Let’s start with the simplest example

<div id="app">
    <h1>{{title}}</h1>
</div>
<script>
    const {createApp} = Vue
createApp({
    data() {
        return {
            title: 'coboy'
        }
    }
}).mount('#app')
</script>
Copy the code

What did VuE3 do?

The runtime – ensureRenderer in the dom

CreateApp comes from runtime-dom/ SRC /index.ts

// actually called by the user
export const createApp = ((. args) = > {
  // Get the renderer first
  constapp = ensureRenderer().createApp(... args)/ /... omit

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

“CreateApp” is one of the ensureRenderer functions that is used to ensure that a renderer is “ensureRenderer” and that the createApp function is included in the result

EnsureRenderer we continue to trace what is going on inside the ensureRenderer function

function ensureRenderer() {
  // If the renderer already exists, return it, otherwise create a renderer, then initialization must take the last part of the code
  return (
    renderer || ((renderer = createRenderer < Node), Element > rendererOptions)
  );
}
Copy the code

We can see that createRenderer comes from run-time core/ SRC /renderer.ts

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

We trace back to the baseCreateRenderer function, and then we look at the baseCreateRenderer function

The runtime – baseCreateRenderer in the core

BaseCreateRenderer comes from Run-time core/ SRC /renderer.ts

function baseCreateRenderer(options: RendererOptions, createHydrationFns? :typeof createHydrationFunctions
) :any {
  // Too much code, nearly 2000 lines, omitted
}
Copy the code

Then we can see the renderer to create the basis of the function is very much of the code, there are nearly 2000 lines of code, is the biggest function of vue3 source code library, it is said that the had some Suggestions especially greatly the function separation, but was especially greatly declined, especially greatly think it is not necessary to excessive design, the function’s duty is to do the renderer, Responsibilities are very clear and do not need to be divided

So from this story, we can also learn that this function does a lot of work for the renderer, but we can ignore the details for now, we just need to focus on the return value of this function

function baseCreateRenderer(options: RendererOptions, createHydrationFns? :typeof createHydrationFunctions
) :any {
	// Too much code, nearly 2000 lines, no details

    // The patch function is used whenever a VNode is converted to a DOM
    const patch: PatchFn = () = > {}

    // ...

    // key function
    const processComponent = () = > {}
    // key function
    const mountComponent: MountComponentFn = () = > {}

    const updateComponent = (n1: VNode, n2: VNode, optimized: boolean) = > {}
    // key function
    const setupRenderEffect: SetupRenderEffectFn = () = > {}

    // ...

    const render: RootRenderFunction = (vnode, container, isSVG) = > {}

    let hydrate: ReturnType<typeof createHydrationFunctions>[0] | undefined


    CreateApp is the result of createAppAPI
    return {
        render, // Reactdom.render is the same as reactdom.render in react, which is used to render vNodes, convert them to dom, and append them to the host
        hydrate, / / for SSR
        createApp: createAppAPI(render, hydrate)
    }
}
Copy the code

After initializing the renderer, return a renderer and execute createApp

// Get the renderer first
constapp = ensureRenderer().createApp(... args);Copy the code

CreateApp is the result of createAppAPI from the baseCreateRenderer function that creates the renderer

CreateAppAPI in the renderer instance

BaseCreateRenderer comes from Runtime-core/SRC/apicReateapp.ts CreateAppAPI receives a Render method when initialized and returns a createApp method

// This method is the factory method of createApp
export function createAppAPI<HostElement> (render: RootRenderFunction, hydrate? : RootHydrateFunction) :CreateAppFunction<HostElement> {
    // Return the App instance
    return function createApp(rootComponent, rootProps = null) {
        if(rootProps ! =null && !isObject(rootProps)) {
            rootProps = null
        }

        const context = createAppContext()
        const installedPlugins = new Set(a)let isMounted = false
		// Get the APP instance outside
        const app: App = (context.app = {
            get config() {
                return context.config
            },

            set config(v) {},// The plugin application method
            use(plugin: Plugin, ... options: any[]) {
                return app
            },
			// Compatible with the mixin method of VUe2
            mixin(mixin: ComponentOptions) {
                return app
            },
		    // Component methodscomponent(name: string, component? : Component): any {return app
            },
			// Instruction method
            directive(name: string, directive? : Directive) {
                return app
            },
            // The most important thing here is the mount methodmount( rootContainer: HostElement, isHydrate? : boolean, isSVG? : boolean ): any {// When initializing, buy mount, go here
                if(! isMounted) {// Build the root component virtual DOM
                    const vnode = createVNode(
                        rootComponent as ConcreteComponent,
                        rootProps
                    )
                    vnode.appContext = context

                    if (isHydrate && hydrate) {
					// Server render goes here
                      hydrate(vnode as VNode<Node, Element>, rootContainer as any)
                    } else {
                        // The spa program goes here
                        render(vnode, rootContainer, isSVG)
                    }
                    isMounted = trueapp._container = rootContainer ; (rootContaineras any).__vue_app__ = app

                    returnvnode.component! .proxy }else if (__DEV__) {

                }
            },

            unmount() {
                if (isMounted) {
                    render(null, app._container)
                    delete app._container.__vue_app__
                } else if (__DEV__) {
                }
            },

            provide(key, value) {
                context.provides[key as string] = value
                return app
            }
        })

        if (__COMPAT__) {
            installAppCompatProperties(app, context, render)
        }

        return app
    }
}
Copy the code

CreateApp initializes a Render, builds a vNode, passes it to Render and passes in the host element rootComponent received at mount.

Override mount method

We walked once and then we came back

export const createApp = ((. args) = > {
    constapp = ensureRenderer().createApp(... args)const {mount} = app
    // Override the mount method, which is also called by the outermost user when the mount method is called
    app.mount = (containerOrSelector: Element | ShadowRoot | string): any= > {
        // Get the template content
        const container = normalizeContainer(containerOrSelector)
        if(! container)return

        const component = app._component
        if(! isFunction(component) && ! component.render && ! component.template) {// Assign the template contents to component.template
            component.template = container.innerHTML
        }

       // Clear the template content before mounting
        container.innerHTML = ' '
        // Mount the same method as createApp in createAppAPI
        const proxy = mount(container, false, container instanceof SVGElement)
        if (container instanceof Element) {
            container.removeAttribute('v-cloak')
            container.setAttribute('data-v-app'.' ')}return proxy
    }

    return app
}) as CreateAppFunction<Element>
Copy the code
Mount method in createAppAPI

To take advantage of closures, the final mount method we perform is the one in createAppAPI above

The key code

// The most important thing here is the mount methodmount( rootContainer: HostElement, isHydrate? : boolean, isSVG? : boolean ): any {// When initializing, buy mount, go here
    if(! isMounted) {// Create the root component virtual DOM
        const vnode = createVNode(
            rootComponent as ConcreteComponent,
            rootProps
        )
        vnode.appContext = context

        // Server-side render
        if (isHydrate && hydrate) {
            hydrate(vnode as VNode<Node, Element>, rootContainer as any)
        } else {
            // Initialization goes here
            render(vnode, rootContainer, isSVG)
        }
        isMounted = trueapp._container = rootContainer ; (rootContaineras any).__vue_app__ = app


        returnvnode.component! .proxy }else if (__DEV__) {

    }
},
Copy the code

The final initialization in the mount method is the render function, which is in the 2,000-line renderer

The key code

// Receive vNodes, convert them to DOM, and append them to the host container
const render: RootRenderFunction = (vnode, container, isSVG) = > {
  if (vnode == null) {
    if (container._vnode) {
      unmount(container._vnode, null.null.true); }}else {
    // Initialization goes here
    // Patch is used whenever a VNode is converted to a DOM
    // When the container._vnode is null during initialization, the initialization logic is used and diff is not performed
    patch(container._vnode || null, vnode, container, null.null.null, isSVG);
  }
  flushPostFlushCbs();
  container._vnode = vnode;
};
Copy the code

This patch is also in the 2000 line renderer

const patch: PatchFn = (
    n1,
    n2,
    container,
    anchor = null,
    parentComponent = null,
    parentSuspense = null,
    isSVG = false,
    slotScopeIds = null,
    optimized = false
) = > {
    if(n1 && ! isSameVNodeType(n1, n2)) { }if (n2.patchFlag === PatchFlags.BAIL) {
    }

    const {type, ref, shapeFlag} = n2
    switch (type) {
        case Text:
            break
            case Comment:
            break
            case Static:
            break
            case Fragment:
            break
            default:
            if (shapeFlag & ShapeFlags.ELEMENT) {

            } else if (shapeFlag & ShapeFlags.COMPONENT) {
                // Initialization goes here
                processComponent(
                    n1,
                    n2,
                    container,
                    anchor,
                    parentComponent,
                    parentSuspense,
                    isSVG,
                    slotScopeIds,
                    optimized
                )
            } else if (shapeFlag & ShapeFlags.TELEPORT) {

            } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {

            } else if (__DEV__) {

            }
    }
Copy the code

Why initialize the else if branch (ShapeFlags.COMPONENT)

Since the mount method in createAppAPI creates the virtual DOM of the root component, ShapeFlags.COMPONENT is true

// When initialized, not yet mounted, go here
if(! isMounted) {// Create the root component virtual DOM
    const vnode = createVNode(
        rootComponent as ConcreteComponent,
        rootProps
    )
    vnode.appContext = context
}
Copy the code

The processComponent function is also in the 2000 line renderer function

    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 {
                // Initialization goes here
                mountComponent(
                    n2,
                    container,
                    anchor,
                    parentComponent,
                    parentSuspense,
                    isSVG,
                    optimized
                )
            }
        } else {
            updateComponent(n1, n2, optimized)
        }
    }
Copy the code
MountComponent: mounts component functions

We finally see the familiar function mountComponent, which mounts component functions

const mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) = > {
  // 1. Create a component instance
  const instance: ComponentInternalInstance = (initialVNode.component =
    createComponentInstance(initialVNode, parentComponent, parentSuspense));
  if (isKeepAlive(initialVNode)) {
  }

  // 2. Component instance setup, equivalent to component initialization, equivalent to vue2's this._init instance property, method initialization, data responsiveness, two lifecycle hooks
  // Attribute declaration, responsivity, etc
  setupComponent(instance);

  if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
  }
  // Update mechanism. Install render function side effect, equivalent to updateComponent+ Watcher
  setupRenderEffect(
    instance,
    initialVNode,
    container,
    anchor,
    parentSuspense,
    isSVG,
    optimized
  );
};
Copy the code
The setupComponent function
export function setupComponent(
  instance: ComponentInternalInstance,
  isSSR = false
) {
  isInSSRComponentSetup = isSSR;

  const { props, children, shapeFlag } = instance.vnode;
  const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT;
  // Initialize Props
  initProps(instance, props, isStateful, isSSR);
  // Initialize slots
  initSlots(instance, children);
  // setup data state processing
  const setupResult = isStateful
    ? setupStatefulComponent(instance, isSSR)
    : undefined;
  isInSSRComponentSetup = false;
  return setupResult;
}
Copy the code
The setupStatefulComponent function handles the setup data state
function setupStatefulComponent(instance: ComponentInternalInstance, isSSR: boolean) {
  const Component = instance.type as ComponentOptions
  // CTX is the familiar this component instance from VUe2
  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
  // 2. call setup()
  const { setup } = Component
  // If the setup function is set
  if (setup) {
    // setupContext context
    const setupContext = (instance.setupContext =
      setup.length > 1 ? createSetupContext(instance) : null)

    currentInstance = instance
    pauseTracking() // Tree shake optimization
    const setupResult = callWithErrorHandling(
      setup,
      instance,
      ErrorCodes.SETUP_FUNCTION,
      [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
    )
    resetTracking() // Reset tree shake optimization
    currentInstance = null
    // Setup may return a Promise
    if (isPromise(setupResult)) {
      if (isSSR) {
      } else if (__FEATURE_SUSPENSE__) {
        instance.asyncDep = setupResult
      } else if (__DEV__) {
      }
    } else {
      handleSetupResult(instance, setupResult, isSSR)
    }
  } else {
    finishComponentSetup(instance, isSSR)
  }
}
Copy the code
FinishComponentSetup function

Note that the finishComponentSetup function is eventually executed whether or not the setup function is set

function finishComponentSetup(instance: ComponentInternalInstance, isSSR: boolean) {
  const Component = instance.type as ComponentOptions
  if (__NODE_JS__ && isSSR) {
  } else if(! instance.render) {// If there is no render function, compile it
    if(compile && Component.template && ! Component.render) { Component.render = compile(Component.template, {isCustomElement: instance.appContext.config.isCustomElement,
        delimiters: Component.delimiters
      })
    }

    instance.render = (Component.render || NOOP) as InternalRenderFunction
    if (instance.render._rc) {
      instance.withProxy = new Proxy(
        instance.ctx,
        RuntimeCompiledPublicInstanceProxyHandlers
      )
    }
  }

  / / compatible vue2.0
  if (__FEATURE_OPTIONS_API__) {
    currentInstance = instance
    applyOptions(instance, Component)
    currentInstance = null}}Copy the code
The applyOptions function handles the VUe2 API
export function applyOptions(
  instance: ComponentInternalInstance,
  options: ComponentOptions,
  deferredData: DataFn[] = [],
  deferredWatch: ComponentWatchOptions[] = [],
  asMixin: boolean = false
) {
  const {
    // composition
    mixins,
    extends: extendsOptions,
    // state
    data: dataOptions,
    computed: computedOptions,
    methods,
    watch: watchOptions,
    provide: provideOptions,
    inject: injectOptions,
    // assets
    components,
    directives,
    // lifecycle can see that vue3 has no beforeCreate, created function and setup instead
    beforeMount,
    mounted,
    beforeUpdate,
    updated,
    activated,
    deactivated,
    beforeDestroy,
    beforeUnmount,
    destroyed,
    unmounted,
    render,
    renderTracked,
    renderTriggered,
    errorCaptured
  } = options

  const publicThis = instance.proxy!
  
  if (beforeMount) {
    // We removed a lot of other code logic, looking at this function alone, it is obvious that we can bind instance.proxy with beforeMount context
    onBeforeMount(beforeMount.bind(publicThis))
  }

}
Copy the code
SetupRenderEffect function

This method wraps the render function as a side effect function and executes it immediately, after which the interface renders, and when the dependent reactive data changes, it reexecutes

  const setupRenderEffect: SetupRenderEffectFn = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) = > {
    UseEffect (fn,[])
    instance.update = effect(function componentEffect() {
      // If isMounted is false for the first time, the initialization process is performed
      if(! instance.isMounted) {// First get the component vNode, which calls the component render
        // This call triggers dependency collection
        // subTree can be understood as the subTree of the current component
        const subTree = (instance.subTree = renderComponentRoot(instance))

        if (el && hydrateNode) {

        } else {
          // Update recursively downward
          // The first time is a full recursive creation
          patch(
            null,
            subTree,
            container,
            anchor,
            instance,
            parentSuspense,
            isSVG
          )
          initialVNode.el = subTree.el
        }

        instance.isMounted = true
      } else {// Update here}}}Copy the code
Conclusion:

Without Watcher in VUe3,Dep becomes very pure, the relationship between responsive data and its associated side effect function

Draw a flow chart to illustrate the initialization of VUe3

We export a function called createApp from a global variable Vue, and then create an instance of createApp. CreateApp basically creates a renderer first. One instance of a renderer that is “ensureRenderer” or a custom Web platform renderer called baseCreateRenderer in runtime-core is returned. The createApp method in the renderer instance creates an app instance with a mount method that parses the transformation template content and mounts it to the host node