vue3初始化component的主要流程
首先,先给出component初始化流程的思维导图,跟着思维导图会对整个流程非常清晰:
注:截图为项目到这一章所有的文件,等等也会给出涉及到文件的相应文件
然后,我们来实现我们组件初始化流程的happy path:
1 我们先创建一个index.html文件,并且在文件中引入main.js
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并且挂载
main.js:
import {app} from './App.js'
// vue3
createApp(app).mount("#app")
3 创建main.js中用到的App.js,文件中主要是写了一个vue组件,由于还没有实现编译功能 所以先直接写render函数,因为本质上 最终也是转化成render函数,其中导出的App中就涉及到了render和setup函数
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
4 patch这里其实是有两条分支的,由于我们今天只讨论component的主要流程,所以我们就先按component去走,然后就是在patch中调用了processComponent处理组件的函数,处理组件中又调用了mountComponent挂载组件函数,并且他们也是一层层的将vnode和container传递进来
5 mountComponent首先通过createComponentInstance创建出组件实例,然后通过setupComponent传入instance对组件进行一个初始化操作,由于这两个都是对组件的操作,所以就抽到component.ts中去
接下来就是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循环去处理组件
然后我们就需要对我们的程序进行有一个打包,然后才能在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的主要流程了~~~