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

摘要

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

准备 demo

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

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

html 复制代码
<!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

js 复制代码
createApp(App).mount('#app')

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

用法和 Vue 一样:

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

App.js

js 复制代码
export const App = {
	setup(){
		return {
			msg: 'World'	
		}
	},
	render(){
		return h('div', {}, 'Hello, ' + this.msg)
	}	
}
  • App 是根组件,本质是一个对象
  • 导出给 main.js 进行挂载
  • 使用组合式 API的写法

创建 app 实例

runtime-core/createApp.js

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

创建虚拟节点

runtime-core/vnode.js

js 复制代码
export function createVNode(type) {
	const vnode = {
		type
	}

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

渲染根组件

runtime-core/createApp.js

diff 复制代码
export function createApp(rootComponent){
	return {
		mount(rootContainer){
			const vnode = createVNode(rootComponent)

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

runtime-core/renderer.js

js 复制代码
export function render(vnode, container){
	patch(vnode, container)
}

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

拆箱

runtime-core/renderer.js

js 复制代码
export function patch(vnode, container){
	processComponent(vnode, container)
}

该函数的作用是拆箱

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

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

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

处理组件

runtime-core/renderer.js

js 复制代码
export function processComponent(vnode, container){
	mountComponent(vnode, container)
}

处理组件也分两种情况:

  • 视图初始化
  • 视图更新

这里先关注初始化

挂载组件

runtime-core/mountComponent.js

js 复制代码
export function mountComponent(vnode) {
	const instance = createComponentInstance(vnode)
}

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

创建组件实例

runtime-core/component.js

js 复制代码
export function createComponentInstance(vnode){
	const component = {
		vnode
	}	

	return component
}

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

初始化组件

runtime-core/component.js

diff 复制代码
export function mountComponent(vnode, container){
	const instance = createComponentInstance(vnode)

+	setupComponent(instance)
}

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

runtime-core/component.js

js 复制代码
export function setupComponent(instance){
	setupStatefulComponent(instance)	
}

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

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

初始化有状态的组件

runtime-core/component.js

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

diff 复制代码
export function mountComponent(vnode, container){
	const instance = createComponentInstance(vnode)

	setupComponent(instance)

+	setupRenderEffect(instance, container)
}

runtime-core/renderer.js

js 复制代码
export function setupRenderEffect(instance, container){
	const subTree = instance.render()	

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

总结

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

预告

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

相关推荐
Summer不秃1 分钟前
Flutter之使用mqtt进行连接和信息传输的使用案例
前端·flutter
旭日猎鹰5 分钟前
Flutter踩坑记录(二)-- GestureDetector+Expanded点击无效果
前端·javascript·flutter
Viktor_Ye12 分钟前
高效集成易快报与金蝶应付单的方案
java·前端·数据库
hummhumm14 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
乐闻x40 分钟前
Vue.js 性能优化指南:掌握 keep-alive 的使用技巧
前端·vue.js·性能优化
一条晒干的咸魚42 分钟前
【Web前端】创建我的第一个 Web 表单
服务器·前端·javascript·json·对象·表单
Amd7941 小时前
Nuxt.js 应用中的 webpack:compiled 事件钩子
前端·webpack·开发·编译·nuxt.js·事件·钩子
生椰拿铁You1 小时前
09 —— Webpack搭建开发环境
前端·webpack·node.js
狸克先生1 小时前
如何用AI写小说(二):Gradio 超简单的网页前端交互
前端·人工智能·chatgpt·交互
baiduopenmap2 小时前
百度世界2024精选公开课:基于地图智能体的导航出行AI应用创新实践
前端·人工智能·百度地图