【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的渲染逻辑,下一节我们会实现这一部分

相关推荐
顽疲4 分钟前
从零用java实现 小红书 springboot vue uniapp(13)模仿抖音视频切换
java·vue.js·spring boot
江号软件分享38 分钟前
有效保障隐私,如何安全地擦除电脑上的敏感数据
前端
web守墓人2 小时前
【前端】ikun-markdown: 纯js实现markdown到富文本html的转换库
前端·javascript·html
Savior`L2 小时前
CSS知识复习5
前端·css
许白掰2 小时前
Linux入门篇学习——Linux 工具之 make 工具和 makefile 文件
linux·运维·服务器·前端·学习·编辑器
中微子6 小时前
🔥 React Context 面试必考!从源码到实战的完整攻略 | 99%的人都不知道的性能陷阱
前端·react.js
中微子7 小时前
React 状态管理 源码深度解析
前端·react.js
加减法原则8 小时前
Vue3 组合式函数:让你的代码复用如丝般顺滑
前端·vue.js
yanlele9 小时前
我用爬虫抓取了 25 年 6 月掘金热门面试文章
前端·javascript·面试
lichenyang4539 小时前
React移动端开发项目优化
前端·react.js·前端框架