This is the fifth day of my participation in the August More text Challenge. For details, see: August More Text Challenge

preface

Prepare a demo

<div id="app"> {{ a }} </div> ... createApp({ setup() { const data = reactive({ a: 1111 }) return { ... toRefs(data) } } }).mount('#app')Copy the code

In this code, the createApp and mount functions complete the vue initialization process, which is divided into two parts: one is to create the app instance, the other is to mount the instance. Come down and take a look.

CreateApp Creates an instance

The function basically does two things

  1. createappThe instance
  2. rewritemountfunction

The vue instance was created in a single line of code

const app = ensureRenderer().createApp(... args)Copy the code

There is a createApp function in a renderer.

So what does ensureRenderer do?

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

You can see that it’s used here to get renderer, and if it doesn’t exist it’s created by createRenderer. So what does createRenderer do?

The trace code eventually shows that createRenderer ultimately calls the baseCreateRenderer function. This function is a huge function, because our main task is to get a renderer, and then get the createApp function from the renderer. So we can first look at the return value:

 return {
   render,
   hydrate,
   createApp: createAppAPI(render, hydrate)
 }
Copy the code

We can clearly see that our createApp function calls createAppAPI, and we pass in render and hydrate when we call them. Both functions are defined in the baseCreateRenderer function. The render function is used to unload and update nodes. The hydrate function is used when SSR is used. I don’t care about it here.

Then we can look at the createAppAPI function:

export function createAppAPI<HostElement>( render: RootRenderFunction, hydrate? : RootHydrateFunction ): CreateAppFunction<HostElement> { return function createApp(rootComponent, rootProps = null) { const context = createAppContext() const installedPlugins = new Set() let isMounted = false // vue Const app: app = (context.app = {//... }) // Return app instanceCopy the code

The createAppAPI function takes two arguments, but internally returns the createApp function directly. The createApp function defines the context and creates the app instance and returns it. We’ve defined some of the common functions in our app and one of them is the mount function.

At this point, the step of creating the app instance is clear:

  1. You get a renderer
  2. Get the createApp from the renderer and call the app instance

Overriding the mount function is to convert the selector into a DOM node as a container and then call the original mount function.

Let’s take a look at how the app defines the mount function. # introduction

Prepare a demo

<div id="app"> {{ a }} </div> ... createApp({ setup() { const data = reactive({ a: 1111 }) return { ... toRefs(data) } } }).mount('#app')Copy the code

In this code, the createApp and mount functions complete the vue initialization process, which is divided into two parts: one is to create the app instance, the other is to mount the instance. Come down and take a look.

CreateApp Creates an instance

The function basically does two things

  1. createappThe instance
  2. rewritemountfunction

The vue instance was created in a single line of code

const app = ensureRenderer().createApp(... args)Copy the code

There is a createApp function in a renderer.

So what does ensureRenderer do?

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

You can see that it’s used here to get renderer, and if it doesn’t exist it’s created by createRenderer. So what does createRenderer do?

The trace code eventually shows that createRenderer ultimately calls the baseCreateRenderer function. This function is a huge function, because our main task is to get a renderer, and then get the createApp function from the renderer. So we can first look at the return value:

 return {
   render,
   hydrate,
   createApp: createAppAPI(render, hydrate)
 }
Copy the code

We can clearly see that our createApp function calls createAppAPI, and we pass in render and hydrate when we call them. Both functions are defined in the baseCreateRenderer function. The render function is used to unload and update nodes. The hydrate function is used when SSR is used. I don’t care about it here.

Then we can look at the createAppAPI function:

export function createAppAPI<HostElement>( render: RootRenderFunction, hydrate? : RootHydrateFunction ): CreateAppFunction<HostElement> { return function createApp(rootComponent, rootProps = null) { const context = createAppContext() const installedPlugins = new Set() let isMounted = false // vue Const app: app = (context.app = {//... }) // Return app instanceCopy the code

The createAppAPI function takes two arguments, but internally returns the createApp function directly. The createApp function defines the context and creates the app instance and returns it. We’ve defined some of the common functions in our app and one of them is the mount function.

At this point, the step of creating the app instance is clear:

  1. You get a renderer
  2. Get the createApp from the renderer and call the app instance

Overriding the mount function is to convert the selector into a DOM node as a container and then call the original mount function.

Let’s take a look at how the app defines the mount function.

Mount Mount

The simplified mount function is as follows

mount( rootContainer: HostElement, isHydrate? : boolean, isSVG? : Boolean): any {// initialize the process if (! isMounted) { const vnode = createVNode( rootComponent as ConcreteComponent, rootProps ) vnode.appContext = context render(vnode, rootContainer, # app._container = rootContainer # app._container = rootContainer Because this component is going to be a traceable responsive object return vnode.ponent! .proxy }Copy the code

We will create a VNode based on rootComponent, then call render to render, and return vnode.component.proxy.

Let’s look at the main render function

Render function

The render function is passed in the createAppAPI function, which is the render function defined in the baseCreateRenderer.

 const render: RootRenderFunction = (vnode, container, isSVG) => {
   patch(container._vnode || null, vnode, container, null, null, null, isSVG)
   container._vnode = vnode
 }
Copy the code

It mainly determines the uninstallation logic or update logic. If vnode does not exist, it is the ummount uninstallation logic; otherwise, it is the patch update logic.

The patch function

The first three parameters of the patch function are n1, n2, and Container, which represent oldVNode, newVNode, and Container, respectively.

The main function of patch is to determine what the new Vnode is according to its type, and then call different functions for different types of nodes to compare the old and new nodes and render them.

Since the createApp function in Demo is passed an object, it is considered a component and processComponent is called.

CreateApp ({/* here is component */})

const patch: PatchFn = (n1, // old VNode n2, // New VNode Container, Anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, slotScopeIds = null, optimized = false ) => { const { type, ref, shapeFlag } = n2 if (shapeFlag & ShapeFlags.COMPONENT) { processComponent( n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ) } }Copy the code

ProcessComponent function

The main thread in this function is whether to update or initialize the mount based on whether there is an old vNode. For our theme, just focus on the mountComponent 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) => {// mountComponent(n2, Container, Anchor, parentComponent, parentSuspense, isSVG, optimized)}Copy the code

MountComponent function

SetupRenderEffect (); setupComponent (); setupRenderEffect (); setupRenderEffect (); setupRenderEffect ();

The setupComponent function is used to initialize props and slots on the current instance and to call the setup function

The setupRenderEffect function creates the render function for the current instance as responsive

const mountComponent: MountComponentFn = (initialVNode, // initialize vNode/new VNode, corresponding to n2 Container, Anchor, parentComponent, parentSuspense, isSVG, optimized ) => { const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance( initialVNode, parentComponent, parentSuspense )) setupComponent(instance) setupRenderEffect( instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized ) }Copy the code

The setupComponent function

The functions props and slots are initialized, and then setupStatefulComponent is called to invoke the setup function

 export function setupComponent(
   instance: ComponentInternalInstance,
   isSSR = false
 ) {
   const { props, children } = instance.vnode
 ​
   initProps(instance, props, isStateful, isSSR)
   initSlots(instance, children)
  
     setupStatefulComponent(instance, isSSR)
  
   return setupResult
 }
Copy the code

Let’s look at the setupStatefulComponent function

SetupStatefulComponent function

Main operations in the function:

  • Proxy the CTX on the instance and bind it to the proxy property of the instance

     instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
    Copy the code

    Proxying CTX on the instance, future CTX operations will be responsive

    We need to use the instance. Proxy object

    Because instance. CTX is different in prod and dev environments

  • Create setupContext context

     const setupContext = (instance.setupContext = createSetupContext(instance)
    Copy the code
  • Setup is called with props and setupContext passed in. This means that the setup function will take these two arguments.

     const setupResult = callWithErrorHandling(
           setup,
           instance,
           ErrorCodes.SETUP_FUNCTION,
           [instance.props, setupContext]
         )
    Copy the code
  • Then we call handleSetupResult to process setupResult

HandleSetupResult function

If setupResult is function, then setupResult is assigned to instance.render

If the type is object, then the result of setupResult via proxyRefs proxy is assigned to instance-setupState.

 instance.setupState = proxyRefs(setupResult)
Copy the code

The function of proxyRefs is to use the setupResult object as a layer of proxy

Makes it easy for users to access values of type REF directly

For example, setupResult has an object of type ref, so users can use count instead of count. Value

Here is the official website said in the automatic deconstruction of Ref type

SetupResult in demo is an object, so it will be proxyRefs.

Finally, the finishComponentSetup function is also called.

FinishComponentSetup function

I did two things in the function

  • ifinstanceThere is no rendering function in the compiler according tocomponent.templateGenerate and assign toinstance.render
  • Compatible with2.xIn theoptions API

At this point, you have seen the main flow in setupComponent. Now you can go back to setupRenderEffect.

SetupRenderEffect function

The main thing in this function is to define instance.update, whose value is the return value of effect, which is a reactive function activeEffect.

 const setupRenderEffect = () => {
   // create reactive effect for rendering
   instance.update = effect(
     function componentEffect() {
             // ...
     }, prodEffectOptions)
 }
Copy the code

Effect is called with the componentEffect function passed in. ComponentEffect is the dependency to be collected.

We also pass a prodEffectOptions in which the scheduler is used to trigger our dependencies. Vue3 Reactive source code

Initialize mount and update patch in componentEffect function.

The initialization process is:

  1. Execute beforeMount hooks

     invokeArrayFns(bm)
    Copy the code
  2. Get the subTree and recursively call Patch to process the subTree

     patch(
         null,
         subTree,
         container,
         anchor,
         instance,
         parentSuspense,
         isSVG
       )
    Copy the code
  3. Execute the Mounted hook

     queuePostRenderEffect(m, parentSuspense)
    Copy the code

When this is done, the APP instance is successfully mounted to the DOM.

conclusion

  1. First createvueInstance, and then mount the instance.
  2. During the mount process, the corresponding virtual object is created based on the incoming component objectDOM.
  3. According to the virtualDOMCreate the corresponding component instance.
  4. And then initializepropsslotsAnd call thesetupFunction.
  5. Generate render function
  6. Compatible with2.xVersion of theoptions APIThe processing of
  7. performbeforeMountHook function
  8. To obtainsubTreeAnd call thepatchTo deal with
  9. callmountedHook function

Follow depth-first throughout the mounting process.

Refer to the link

mini-vue