This chapter does not involve the compilation module, only talk about the runtime environment, Vue3 basic operation principle. Why is it called big talk? Because it’s not elaborate.

Create a Vue3 application

We know that the compiled application code for Vue3 looks like this:

const App = {
    setup(){},render() {
        return createVNode()
    }
}
Copy the code

You can then run the entire application and render the view to the browser by making the following call.

const app = createApp(App)
app.mount("#app")
Copy the code

So what is going on at the bottom of Vue3?

Create the renderer

First of all, the bottom layer of createApp is to create a renderer through the createRenderer. The renderer is the creation, deletion, and modification of the elements we wrote; The creation, deletion, and modification of element attributes. So different platforms have different apis for manipulating elements, so when we createRender, we need to create renderers based on our platform’s element manipulation features. Renderers are created using the Run-time dom package provided by Vue3 by default. This run-time DOM is used to create renderers based on the element manipulation features of our browser.

// Note that the following code is a simple simulation. The actual code is much more complex, but the principle is the same.
const renderer = createRenderer({
    createElement, 
    createTextNode,
    removeChild,
    insertBefore,
})
Copy the code

CreateElement, createTextNode, removeChild, insertBefore, and so on are native apis for browsers.

So what does this createRenderer look like?

// Note that the following code is a simple simulation. The actual code is much more complex, but the principle is the same.
function createRenderer() {
    
    function render() {}// ...
    
    return {
        // ...
        createApp: createAppAPI(render)
    }
}
Copy the code

We can see that the createRenderer function returns an object with an important method called createApp, which was created by another factory function called createAppAPI. So what does createAppAPI look like?

CreateApp method in the renderer object

// Note that the following code is a simple simulation. The actual code is much more complex, but the principle is the same.
function createAppAPI(render) {
    return function creatApp(rootComponent) {
        const app = {
            mount(rootContainer) {
                const vnode = createVNode(rootComponent)
                render(vnode, rootContainer)
            }
        }
        return app
    }
}
Copy the code

We can see that in the createAppAPI factory function there is a creatApp method that actually creates the Vue3 application. In this method, we create a Vue3 instance application app with a mount method that we often use outside. So when app.mount(‘#app’) is called outside, it creates the VNode of the rootComponent from the rootComponent object passed in by the creatApp(app).

The render method in the renderer

Once the root component’s VNode is created, the render method in the renderer mounts the root component’s VNode. We can see that the render method is inside the createRenderer function, and the createApp method in the returned renderer object is a closure that passes the createAppAPI(Render) argument to the mount method, You can then use it when creating Vue3 applications outside the home.

At this point, we’ll go back to the renderer function and see what the render function does

// Note that the following code is a simple simulation. The actual code is much more complex, but the principle is the same.
function createRenderer() {
    
    function render(vnode) {
        patch(null,vnode)
    }
    // N1 is the old VNode,n2 is the new VNode
    function patch(n1,n2) {
        const { shapeFlag } = n2
        switch(shapeFlag){
            case 'element':
                processElement(n1,n2)
            case 'component':
                processComponent(n1,n2)
        }
    }
    
    // ...
    
    return {
        // ...
        createApp: createAppAPI(render)
    }
}
Copy the code

The patch method is called in the Render method. As an aside, the Render method is similar to the Reactdom. render method in React, which renders an application object to a node.

Patch method in the renderer

The patch method determines which process to execute based on the shapeFlag on the new VNode. It is clear that n2 is the VNode of the root component when it first comes in, so it follows the processComponent process. So what’s going on inside processComponent?

// Note that the following code is a simple simulation. The actual code is much more complex, but the principle is the same.
function processComponent(n1, n2) {
    if(! n1) { mountComponent(n2) }else {
        updateComponent(n1, n2)
    }
}
Copy the code

The processComponent determines whether an old VNode exists and decides whether to mount it or update it. The initial component is mountComponent(n2).

Component initialization process

Creating a component instance

// Note that the following code is a simple simulation. The actual code is much more complex, but the principle is the same.
function mountComponent(initialVNode) {
    const instance = createComponentInstance(initialVNode)
    setupComponent(instance)
    setupRenderEffect(instance)
}
Copy the code

MountComponent creates an instance of the current component VNode and initializes the instance setupComponent(instance).

Implement component proxy objects

// Note that the following code is a simple simulation. The actual code is much more complex, but the principle is the same.
function setupComponent(instance) {
    initProps()
    initSlots()
    instance.proxy = new Proxy({ _:instance }, {
        get({ _: instance}, key) {
            const { setupState, props } = instance
            if(hasOwn(setupState, key)) {
                return setupState[key]
            } else if(hasOwn(props, key)) {
                return props[key]
            }
        }
    })
    instance.setupState = instance.VNode.setup()
}
Copy the code

In setupComponent, we initialize props and slots. Then we use the Proxy to create a Proxy object on the component instance object. When we call the render method of the component to create the component element VNode, we access the data returned by the component setup method. It is obtained through the getter method of the proxy object. Using the simulation code above, we can clearly see the value of a key to be accessed in the future, whether the key is in the object returned by setup or in the props.

After initializing the component instance object, the setupRenderEffect function is executed.

Update logic setupRenderEffect

// Note that the following code is a simple simulation. The actual code is much more complex, but the principle is the same.
function setupRenderEffect(instance) {
    instance.update = effect(() = > {
        if(! instance.isMounted) {const subTree = instance.render.call(instance.proxy)
           patch(null, subTree)
           instance.isMounted = true
        } else {
            
           const subTree = instance.render.call(instance.proxy)
           const prevSubTree = instance.subTree
           patch(prevSubTree, subTree)
        }
    })
}
Copy the code

The setupRenderEffect function makes use of the Effect API in the ReActivity library. The effect API takes a function as an argument, wraps the function as a side effect function, executes it once, and returns the function itself, called runner. Obviously, setupRenderEffect assigns the returned Runner function to the update method on the component instance, so that the update method of the component instance can be called directly in the future.

The first step in the setupRenderEffect function must be the unmounted branch if(! The instance. IsMounted). VNode (subTree); VNode (subTree); VNode (subTree); VNode (subTree); VNode (subTree); And the template we wrote in the.vue file will eventually be compiled into the render function, which will call the setup or props data, which binds the proxy object on the component instance via the call method. This lets you get data from setup or props via proxy objects. This is clear when you look at what the setupComponent method does above.

When we mount the VNode of component elements through the patch method after creating the VNode of component elements, our process will return to the patch method, so the rendering of VNode is a process of constantly calling the patch method.

At this point, we return to the patch method.

Element initialization process

// Note that the following code is a simple simulation. The actual code is much more complex, but the principle is the same.
// N1 is the old VNode,n2 is the new VNode
function patch(n1,n2) {
    const { shapeFlag } = n2
    switch(shapeFlag){
        case 'If it's element-element':
            processElement(n1,n2)
        case 'If component-component':
            processComponent(n1,n2)
    }
}
Copy the code

At this point in the mount process, n1 does not exist yet, and the VNode of the component element is rendered, so the processElement flow is followed naturally.

In fact, the shapeFlag of a VNode is calculated by bit operation. When creating a VNode, the shapeFlag of a VNode is determined by the type of the VNode. If type is String then shapeFlag is the element type, and if type is Object then shapeFlag is the component.

// Note that the following code is a simple simulation. The actual code is much more complex, but the principle is the same.
function processElement(n1,n2) {
    if(! n1) { mountElement(n2) }else {
       updateElment(n1, n2)
    }
}
Copy the code

The process in processElement is similar to the processComponent process in that it determines whether an old VNode exists and then branches the process. If there is no old VNode, mountElement is initialized. If there is an old VNode, updateElement is initialized. The famous diff algorithm takes place in updateElement. We are now in the initialization process, so let’s look at what’s going on inside the mountElement.

// Note that the following code is a simple simulation. The actual code is much more complex, but the principle is the same.
function mountElement(vnode) {
    const el = hostCreateElement(vnode.type)
    const { children } = vnode
    if(if the child is text) {// Create text directly
      el.textContent = children
    } else if(If children are arrays) {// Continue to initialize the child
      mountChildren(vnode.children)
    }
    const { props } = vnode
    for (const key in props) {
      const val = props[key]
      // Create an element attribute
      hostPatchProp(el, key, null, val)
    }
    // Insert the element into the host
    hostInsert(el, container, anchor)
}
Copy the code

Note that the hostCreateElement, hostPatchProp and hostInsert methods in mountElement are platform apis that are passed in to handle elements according to the platform characteristics when creating the renderer.

Create element hostCreateElement

The first element is created based on VNode’s type type, which in browsers can be DOM elements like div, SPAN, and P. Once the element is created, continue to create child nodes within the element.

If VNode’s children is a text node, it creates the text node directly. If VNode’s children is an array, it continues to initialize the children node.

Create the hostPatchProp property

If there are props, then the attributes of the element are created. In the browser platform, the setAttribute and removeAttribute apis are used for processing.

Insert into the host element hostInsert

Finally, the created element is inserted into the host element with insertBefore

Example Create mountChildren

If the children of VNode is an array, we continue to initialize the children node.

// Note that the following code is a simple simulation. The actual code is much more complex, but the principle is the same.
function mountChildren(children) {
    children.forEach((v) = > {
      patch(null, v)
    })
}
Copy the code

The main thing is that if the child has a component VNode in it, the patch method will continue the processComponent process to create the component instance, initialize it, The component element VNode is generated and then the patch method is called to render the component element VNode, and so on and so on until all the VNodes have been rendered, at which point the view is rendered on the page.

At this point, the Vue3 initialization process is complete.

Update process

We have already talked about setupRenderEffect function, so let’s talk about this function in detail. The setupRenderEffect function uses the Effect API to create an update mechanism for the rendering function.

// Note that the following code is a simple simulation. The actual code is much more complex, but the principle is the same.
function setupRenderEffect(instance) {
    instance.update = effect(() = > {
        if(! instance.isMounted) {const subTree = instance.render.call(instance.proxy)
           patch(null, subTree)
           instance.isMounted = true
        } else {
            
           const subTree = instance.render.call(instance.proxy)
           const prevSubTree = instance.subTree
           patch(prevSubTree, subTree)
        }
    }, {
        scheduler() {
            queueJobs(instance.update)
        }
    })
}
Copy the code

For those of you who are not familiar with the effect function, the first parameter receives a function wrapped as a side effect function, and the second parameter receives a scheduler function as a parameter. When the dependent data inside is updated, The side function is executed internally again, and the scheduler is determined before the side function is executed. If there is a scheduler, the side function is not executed and the scheduler is executed instead. The reason for doing this is to put all the update functions in an asynchronous task queue, and then execute the update functions in the asynchronous task queue to update vNodes. This is also known as the internal execution mechanism of nextTick function.

When executing the instance.update function in the asynchronous task queue, go to the else process in the dependent function in the effect function in setupRenderEffect, which is the code below.

const subTree = instance.render.call(instance.proxy)
const prevSubTree = instance.subTree
patch(prevSubTree, subTree)
Copy the code

At this time, re-execute the render method in the component instance to generate the new component element VNode, add the previous VNode, at this time, the new and old VNode is formed, and then perform the patch method to render the VNode. Then go to the patch method and follow the respective processElement and processComponent processes, and then go to the processElement process and follow the process of updating element nodes, which is where the Vue Diff algorithm occurs. Vue Diff is also a big topic. Without further elaboration, you can read my previous article to understand the diff algorithm in VUe3 based on The Mini-Vue of Big Trego. The processComponent method goes through the process of updating components. Component updating is also a big topic, and we will talk more about the mechanism of component updating at some time.

So this big talk Vue3 source analysis here, the main process is these, free to boast Vue3 other modules, any module can be discussed in detail.