likes
comments
collection
share

【vue3 原理】初始化组件流程

作者站长头像
站长
· 阅读数 10

摘要

这篇文章的内容包括:调用 createApp 时初始化组件的主流程、虚拟节点的本质及作用和拆箱的概念

准备 demo

在编写核心代码先准备测试用的 demo

index.html 作为 Vue 的容器,引入入口文件 main.js,并指定为 typemodule,接下来使用 ESM 模块规范

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="main.js" type="module"></script>
  </body>
</html>

以上文件有三个注意点:

  • 需要一个 idapp 或者任意字符串的元素,作为应用的根容器元素
  • 引入入口文件 main.js,应用初始化的逻辑都会从这里开始
  • 指定 typemodule是因为入口文件使用 ESM 模块规范

main.js

createApp(App).mount('#app')

其作用和前面提到一样,初始化应用

用法和 Vue 一样:

  • createApp 传入根组件创建 app 实例
  • 然后使用 app 实例上的 mount 方法将应用挂载到根容器元素上

App.js

export const App = {
	setup(){
		return {
			msg: 'World'	
		}
	},
	render(){
		return h('div', {}, 'Hello, ' + this.msg)
	}	
}
  • App 是根组件,本质是一个对象
  • 导出给 main.js 进行挂载
  • 使用组合式 API的写法
  • render最终要返回虚拟节点h函数就是用来创建虚拟节点

创建 app 实例

runtime-core/createApp.js

export function createApp(rootComponent){
	return {
		mount(rootContainer){
			const vnode = createVNode(rootComponent)
		}
	}
}
  • 接收根组件并返回带有mount方法的对象,这个对象就是 app 实例
  • mount方法接受一个根容器
  • mount方法内部需要先调用createVNode将组件对象转换成vnode对象,即虚拟节点,因为之后不管是视图的初始化操作还是更新操作都要基于vnode进行,本质就是操作对象,后面统一称呼为vnode

创建虚拟节点

runtime-core/vnode.js

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

	return vnode
}
  • 虚拟节点本质是对象
  • type 属性表示虚拟节点的类型,因为除了组件类型还有元素等类型需要区分处理
  • props 表示组件或元素的接收的props
  • children表示组件或元素的子节点

h.js

export function h(type, props, children){
	return createVNode(type, props, children)
}

前面[[初始化组件流程#准备 demo|demo]]使用到的h函数实际就是createVNode的简写形式,因为组件的render方法一定要返回虚拟节点,简写形式更加方便用户调用

渲染根组件

runtime-core/createApp.js

export function createApp(rootComponent){
	return {
		mount(rootContainer){
			const vnode = createVNode(rootComponent)

+			render(vnode, rootContainer)
		}
	}
}
  • 将前面得到的vnode交给render函数处理接下来的操作,表示开始初始化根组件,
  • rootContianer作为根容器元素,后面挂载时需要用到

runtime-core/renderer.js

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

render函数拿到根组件vnode要做的只有一件事,就是调用patch函数对根组件进行拆箱,先继续往下看

拆箱

runtime-core/renderer.js

export function patch(vnode, container){
	processComponent(vnode, container)
}

该函数的作用是拆箱

因为组件由一层层嵌套的结构组成,组件既可以嵌套普通元素,也可以嵌套组件

组件可以看成是一个箱子,这个箱子里包含了一些物品的同时也包含了另外一些小箱子,这些小箱子里又包含了另外一些物品,拆箱顾名思义就是将箱子一个个打开,把里面的物品按照原来的层次结构拿出来摆放在桌子上,而这个桌子就是我们指定的根容器

实际表现就是根据vnode的类型处理不同的逻辑,然后决定是否需要递归拆箱,这里我们先实现处理vnode是组件类型的逻辑

处理组件

runtime-core/renderer.js

export function processComponent(vnode, container){
	mountComponent(vnode, container)
}

处理组件也分两种情况:

  • 视图初始化
  • 视图更新

这里先关注初始化

挂载组件

runtime-core/mountComponent.js

export function mountComponent(vnode) {
	const instance = createComponentInstance(vnode)
}

挂载组件首先要将基于组件类型的vnode转换成组件实例,因为和普通元素不同,作为组件之后要保存各种属性,比如setup返回的结果,是否已挂载、组件代理对象等等,后面会一点点补全

创建组件实例

runtime-core/component.js

export function createComponentInstance(vnode){
	const component = {
		vnode
	}	

	return component
}

组件实例就是一个对象,目前只需要保存vnode的数据即可

初始化组件

runtime-core/component.js

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

+	setupComponent(instance)
}

前面拿到了组件实例,接下来需要初始化组件得到各种数据,保存在组件实例

runtime-core/component.js

export function setupComponent(instance){
	setupStatefulComponent(instance)	
}

初始化组件时有两种情况需要判断:

暂时先考虑处理有状态的组件

初始化有状态的组件

runtime-core/component.js

export function setupStatefulComponent(instance){
	const { setup,render } = instance.vnode.type
	const setupResult = setup()	
	instance.setupState = setupResult
	instance.render = render
}
  • 这里假设组件保证有setup方法和render
  • 调用setup获取返回结果作为状态保存到组件实例
  • render只保存到组件实例上,等到后面调用

调用组件 render

runtime-core/component.js

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

	setupComponent(instance)

+	setupRenderEffect(instance, container)
}

runtime-core/renderer.js

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

	patch(subTree, container)
}
  • 经过前面初始化组件后, 组件实例上已经有状态和render方法
  • 调用render获取下一层的vnode进行递归拆箱

总结

经过上面的步骤,我们已经得到了初始化组件的大致流程,用一张流程图概括如下:

【vue3 原理】初始化组件流程

预告

到这里我们还无法从视图上看到效果,因为对根组件拆箱后只是得到了下一个层元素类型的vnode,没有实现对元素类型vnode的渲染逻辑,下一节我们会实现这一部分

转载自:https://juejin.cn/post/7266421018125697065
评论
请登录