Vue3 渲染流程

app.mount时vue做了些什么

首先创建一个App

import { createApp } from 'vue';
import App from './App.vue';
const app = createApp(App);

这个 createApp 是由 createAppAPI 返回的高阶函数:

function createAppAPI<HostElement>(
  render: RootRenderFunction,
  hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
	return function createApp(rootComponent, rootProps = null) {...}
}

render 是渲染实现,vue 默认使用 @vue/runtime-dom 中浏览器的渲染实现

[!NOTE]

从这里可以看出,vue3 分离了渲染器,这也让其拥有了作为跨平台框架的能力

createApp 最后会返回一个 Vue 实例,我们将会调用其 mount 方法渲染到指定元素上

app.mount('#app');

mount 中,先创建 vnode 节点

const vnode = createVNode(
	rootComponent as ConcreteComponent,
	rootProps
)

创建 vnode 节点后,就是渲染

render(vnode, rootContainer, isSVG)

render 就是官方的浏览器渲染实现,其内部实现如下

const render: RootRenderFunction = (vnode, container, isSVG) => {
	if (vnode == null) {
		if (container._vnode) {
			unmount(container._vnode, null, null, true)
		}
	} else {
		patch(container._vnode || null, vnode, container, null, null, null, isSVG)
	}
	flushPostFlushCbs()
	container._vnode = vnode
}
  • 如果节点为 null ,则将节点从当前容器 dom 上卸载
  • 否则就进行 patch(打补丁)
  • 最后执行 Post 队列中的任务

[!NOTE] Post 队列

watchEffect 中的 flush 会指定响应依赖的执行时机,Post 队列中的任务需要在选然后执行

关键就是这里的 patch,在这里面会判断 vnode 的类型

switch (type) {
	case Text:
		...
		break
	case Comment:
		...
		break
	case Static:
		...
		break
	case Fragment:
		...
		break
	default:
		...

由于我们还只是抽象的组件,还未渲染,这里会走到 default 中,在这里面判断我们传入的是组件后,会调用 processComponent 方法:

if (shapeFlag & ShapeFlags.ELEMENT) {
	...
} else if (shapeFlag & ShapeFlags.COMPONENT) {
	processComponent(...)
} else {
	...
}

接着在里面判断是初次渲染后,会接着调用 mountComponent 方法,在里面创建组件实例

const instance: ComponentInternalInstance =
	compatMountInstance ||
	(initialVNode.component = createComponentInstance(
		initialVNode,
		parentComponent,
		parentSuspense
	))

mountComponent 中会调用 setupComponent 方法,该方法中进行了很多初始化操作

  • 初始化 props
  • 初始化插槽
  • 执行 setup 函数
  • 根据 setup 返回值或者模板属性,编译 render 渲染函数

执行完 setupComponent 后,走到接收该实例的 setupRenderEffect 方法

里面定义了组件渲染、更新的方法 componentUpdateFn,该方法由两百多行,非常的重要,Vue 使用该方法在这里新建了一个响应式副作用,双向绑定的魔法在此展开

// create reactive effect for rendering
const effect = (instance.effect = new ReactiveEffect(
	componentUpdateFn,
	() => queueJob(update),
	instance.scope // track it in component's effect scope
))
const update: SchedulerJob = (instance.update = () => effect.run())
update.id = instance.uid
update() // 执行渲染,渲染过程中使用的响应式变量都会被计入依赖

componentUpdateFn 中,如果我们是首次渲染,则会调用 renderComponentRoot 构建 vnode

const subTree = (instance.subTree = renderComponentRoot(instance))

然后执行 patchvnode 渲染到浏览器中,完成首次渲染

反向链接


图谱

LeetCodeObsidian我的作品技术蓝色协议(日服)FLCL无职转生~到异世界就拿出真本事~混沌武士2023-06-062023-06-102023-06-132023-07-02Antd NoteBookCPU性能天梯图CSS NoteBookGFWIndexedDB 读写地狱JS 事件循环JS 原型链JS 运算符LaTeX NoteBookLeetCode Rust - 1 - 两数之和LeetCode Rust - 122 - 买卖股票的最佳时机 IILeetCode Rust - 125 - 验证回文串LeetCode Rust - 136 - 只出现一次的数字LeetCode Rust - 189 - 轮转数组LeetCode Rust - 217 - 存在重复元素LeetCode Rust - 242 - 有效的字母异位词LeetCode Rust - 26 - 删除有序数组中的重复项LeetCode Rust - 283 - 移动零LeetCode Rust - 344 - 反转字符串LeetCode Rust - 350 - 两个数组的交集 IILeetCode Rust - 36 - 有效的数独LeetCode Rust - 387 - 字符串中的第一个唯一字符LeetCode Rust - 48 - 旋转图像LeetCode Rust - 66 - 加一LeetCode Rust - 7 - 整数反转LeetCode Rust - 8 - 字符串转换整数 (atoi)Rust NoteBookYAMLcargo 配置hugo-obsidian 元数据解析问题safari 移动端适配发布同步国际邮箱安装 mingw显卡性能天梯图注册日本邮箱科学上网第三方插件配置 Ruby 环境🤖Gmail🤖Netch🤖Obsidian Git🤖Outlook🤖Plugin Proxy🤖epub.js🤖pdf.js🤖yuè - Web 阅读器🤖加速器HomeReact18 diff 算法Vue3 diff 算法Vue3 响应式系统Vue3 渲染流程CssMarkdownReactRustViteVueWebpack下载万代南梦宫启动器下载蓝色协议注册万代南梦宫账号🤖setup CliLeetcode originLeetcode time&space