第十八章 vue3初始化component的主要流程

508 阅读4分钟

vue3初始化component的主要流程

首先,先给出component初始化流程的思维导图,跟着思维导图会对整个流程非常清晰:

image.png

image.png

注:截图为项目到这一章所有的文件,等等也会给出涉及到文件的相应文件

image.png

然后,我们来实现我们组件初始化流程的happy path:

1 我们先创建一个index.html文件,并且在文件中引入main.js

image.png

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="main.js" type="module"></script>
</head>
<body>

</body>
</html>

2 创建引入的main.js,其中main.js中主要是使用vue3的语法创建app并且挂载

image.png

main.js:

import {app} from './App.js'
// vue3

 createApp(app).mount("#app")

3 创建main.js中用到的App.js,文件中主要是写了一个vue组件,由于还没有实现编译功能 所以先直接写render函数,因为本质上 最终也是转化成render函数,其中导出的App中就涉及到了render和setup函数

image.png

App.js

export const App = {
    // .vue
    // <template></template> 最总转化成render函数
    // render 由于还没有实现编译功能 所以先直接写render函数
    render() {
        // ui
        return h('div','hi, '+this.msg)
    },

    setup(){

        return{
            msg:'mini-vue'
        }
    }
}

以上就是我们正常在使用vue3时候,基本走的一个流程

接下来我们就来实现这个component的初始化流程:

1 在main.js中我们使用了createApp传入app组件生成对象然后调用对象中mount方法,将其挂载到#app上。因此我们我们就需要导入一个createApp函数,并且createApp调用后会返回一个对象,对象中是有mount函数的

createApp.ts

import { render } from "./render"
import { createVNode } from "./vnode"

export function createApp(rootComponent){
    return {
        mount(rootContainer){
            // 先 vnode
            // component -> vnode
            // 所有的逻辑操作 都会基于 vnode 做处理

            const vnode = createVNode(rootComponent)

            render(vnode,rootContainer)
        }
    }
}

2 在createApp中可以看到,我们使用了一个外部引入的createVNode,注释中也解释道了后面所有的逻辑操作,都会基于 vnode 做处理,因此我们把从createApp中传入的组件转化成vnode

vnode.ts

export function createVNode(type,props?,children?){
    const vnode = {
        type,
        props,
        children
    }
    return vnode
}

3 获得vnode后我们就开始我们的组件初始化主流程了,还记得上面的那个思维导图吗,很清晰的看出它是从render开始,然后在render函数中我们调用了patch

image.png

4 patch这里其实是有两条分支的,由于我们今天只讨论component的主要流程,所以我们就先按component去走,然后就是在patch中调用了processComponent处理组件的函数,处理组件中又调用了mountComponent挂载组件函数,并且他们也是一层层的将vnode和container传递进来

image.png

5 mountComponent首先通过createComponentInstance创建出组件实例,然后通过setupComponent传入instance对组件进行一个初始化操作,由于这两个都是对组件的操作,所以就抽到component.ts中去

image.png

接下来就是setupComponent中的一些初始化和setup的操作

5.1 setupComponent函数中我们需要初始化props和slots,然后调用setupStatefulComponent

5.2 调用setupStatefulComponent对setup函数的处理,存在setup我们就调用并且把值传入到handleSetupResult函数中

5.3 handleSetupResult函数中对setupResult结果进行判断,如果为对象就给他赋值到实例tupState中,然后调用finishComponentSetup

5.4 调用finishComponentSetup,实例上用户写了render函数,就把render函数挂载到instance实例上

component.ts

export function createComponentInstance(vnode) {
    const component = {
        vnode,
        type:vnode.type
    }
    return component
}

export function setupComponent(instance) {
    // initProps
    // initSlots

    setupStatefulComponent(instance)
}

function setupStatefulComponent(instance){
    const Component = instance.type

    const {setup} = Component

    if(setup){
       const setupResult = setup()

       handleSetupResult(instance,setupResult)
    }
}

function handleSetupResult(instance,setupResult){
    // function Object

    if(typeof setupResult === 'object'){
        instance.setupState = setupResult
    }

    finishComponentSetup(instance)
}

function finishComponentSetup(instance){
    const Component = instance.type
    if(Component.render){
        instance.render = Component.render
    }
}

6 接着在mountComponent函数中调用setupRenderEffect,setupRenderEffect中就调用instance上的render函数获取虚拟节点,然后再继续patch循环去处理组件

image.png

然后我们就需要对我们的程序进行有一个打包,然后才能在index.html中进行测试

总结一下整体的流程:

render -> patch -> processComponent -> mountComponent -> (createComponentInstance,setupComponent,setupRenderEffect -> patch) 递归调用回去

setupComponent子流程:

setupComponent -> setupStatefulComponent -> handleSetupResult -> finishComponentSetup

render.ts:

import { createComponentInstance, setupComponent } from "./components"

export function render(vnode,container){
    // patch

    patch(vnode,container)
}

function patch(vnode,container){
    // 处理组件
    processComponent(vnode,container)
}

function processComponent(vnode,container){

    mountComponent(vnode,container)
}

function mountComponent(vnode,container) {
    const instance = createComponentInstance(vnode)

    setupComponent(instance)
    setupRenderEffect(instance,container)
}

function setupRenderEffect(instance,container) {
    const subTree = instance.render()
    patch(subTree,container)
}

最后一个要注意的点是

其实我们组件并不会真实的渲染出来,渲染的是组件内部render函数内部的一些element的值,所以我们render函数调用后返回出来的虚拟节点树要再次进行一个patch操作,然后判断类型,如果还是组件就继续对他进行一个拆箱的操作,如果是element类型就进行一个渲染

好了,以上就是我们component的主要流程了~~~