第一章 初识Runtime
前言
当设计一个框架的时候,我们有三种选择:纯运行时的、运行时 + 编译时的或纯编译时的。
我们先聊聊纯运行时的框架。
假设我们设计了一个框架,它提供 一个 Render
函数,用户可以为该函数提供一个树型结构的数据对象,然后 Render 函数会根据该对象递归地将数据渲染成 DOM
元素。我们规定树型结构的数据对象如下:
const obj = {
tag: 'div',
children: [
{
tag: 'span', children: 'hello world' }
]
}
每个对象都有两个属性:tag
代表标签名称,children
既可以是一个数组(代表子节点),也可以直接是一段文本(代表文本子节点)。接着,我们来实现 Render
函数:
function Render(obj, root) {
const el = document.createElement(obj.tag)
if (typeof obj.children === 'string') {
const text = document.createTextNode(obj.children)
el.appendChild(text)} else if (obj.children) {
// 数组,递归调用 Render,使用 el 作为 root 参数
obj.children.forEach((child) => Render(child, el))
}
// 将元素添加到 root
root.appendChild(el)
}
const obj = {
tag: 'div',
children: [
{
tag: 'span', children: 'hello world' }
]
}
// 渲染到 body 下
Render(obj, document.body)
在浏览器中运行上面这段代码,就可以看到我们预期的内容。
现在我们回过头来思考一下用户是如何使用 Render 函数的。可以发现,用户在使用它渲染内容时,直接为Render 函数提供了一个树型结构的数据对象。这里面不涉及任何额外的步骤,用户也不需要学习额外的知识。但是有一天,你的用户抱怨说:“手写树型结构的数据对象太麻烦了,而且不直观,能不能支持用类似于 HTML 标签的方式描述树型结构的数据对象呢?”你看了看现在的 Render 函数,然后回答:“抱歉,暂不支持。”实际上,我们刚刚编写的框架就是一个纯运行时的框架。
以上摘自《Vue3设计与实现》
根据上文再结合之前我们已经学习的@vue/reactivity
包,对Vue
响应式的原理有了深刻理解,那么这些副作用函数和响应式数据到底是怎么在Vue
中自动化渲染的呢?
答案: 就是接下来要学习的
@vue/runtime-core包
。
学习完这个包之后,我们就会对 Vue
的挂载和更新有更加深刻的理解。
接下来还会涉及到Vue3
中的虚拟节点、diff算法、组件化、nextTick
等重要知识点。
好了那么开始源码学习吧~
runtime-core模块
runtime-core
的核心流程主要对是render
函数的调用。在render
函数中最重要的就是两个算法:patch算法和diff算法
patch算法
: 会遍历所有VNode
虚拟节点,根据节点类型去运行相应的渲染操作,然后把虚拟节点一一渲染挂载到根节点上。
diff 算法
: 会去对比新老虚拟DOM树
,通过双端对比过滤出中间乱序部分,对乱序部分使用最长递增子序列算法求得最长递增的新DOM树
,根据这个递增的新DOM树
为基础,在其基础上 添加和移动DOM树
中其他乱序节点。
后续章节讲到diff算法时,会画图做演示,方便大家理解!
VNode
VNode
就是我们常说的虚拟 DOM 对象
那么我们怎么创建VNode
呢?
createVNode
函数就是用来创建虚拟 DOM 节点
的辅助函数, 它的基本实现类似于:
function createVNode(tag, props, children) {
const key = props && props.key
props && delete props.key
return {
tag,
props,
children,
key
}
}
他最主要包括type
,props
,children
三部分
举个例子:
在vue
模板中这段HTML
结构
<div id="foo">
<p class="bar">{
{ text }}</p>
</div>
最后会被编译成
render() {
return createVNode('div', {
id: 'foo' }, [
createVNode('p', {
class: 'bar' }, text, PatchFlags.TEXT) //PatchFlags.TEXT 就是补丁标志
])
}
PatchFlags.TEXT
就是补丁标志
这个标志很重要,它会在patch
算法中被识别并进入相应的初始化阶段
那么生成了虚拟dom对象后,我们就可以进入下一步,patch算法环节了。
patch算法
首先我们先看下源码中的runtime-core
文件夹下renderer.ts
文件找到patch
函数。
其实patch
算法内部就是判断我们生成好的VNode
的类型,然后根据类型去做一些渲染初始化操作
渲染一般分3步:创建元素节点,添加元素属性,挂载到对应的节点上。
可以看到源码判断了很多种情况,包括Text、Static等类型和SUSPENSE、TELEPORT、Fragment等组件
上文提到patch
算法通过判断VNode
的类型进行不同的渲染逻辑
我们刚起步,所以主要重点讲一讲runtime-core
初始化Element
和Component
的这两种类型,它们两对应processElement
和processComponent
两个初始化函数,那么这两个函数运行逻辑是怎么样呢?
我们看一下patch
算法在识别Element
和Component
运行过程的流程图:
了解了patch
算法运行的流程图之后,下一章节我们就可以正式的开始,初始化Component
逻辑了,也就是源码中的processComponent(vnode, container)
函数。