《Vue.js设计与实现》笔记 第7章:渲染器的设计
Vue.js设计与实现 第7章:渲染器的设计
本章导读
Vue整体流程:
响应式系统
数据变化
↓
触发更新
渲染器
VNode
↓
真实DOM
Vue核心模块:
Vue
├── Reactivity
│ └── 数据响应式
│
├── Renderer
│ └── VNode渲染
│
└── Compiler
└── Template编译
本章重点:
- 渲染器基本结构
- 挂载元素
- 更新元素
- Diff算法基础
- 子节点处理
- 组件渲染基础
一、什么是渲染器
Renderer定义
渲染器负责:
把VNode转换成真实DOM
流程:
VNode
↓
Renderer
↓
DOM
VNode是什么
VNode:
Virtual Node
本质:
const vnode = {
type:'div',
props:{
id:'app'
},
children:'hello'
}
描述:
一个虚拟DOM节点
二、渲染器基本结构
createRenderer
Vue内部:
const renderer =
createRenderer(options)
options提供:
{
createElement,
setElementText,
insert
}
原因:
让渲染器与平台无关。
例如浏览器:
createElement(){
return document.createElement()
}
未来:
Canvas
Native
小程序
都可以替换。
三、patch函数
核心函数
patch(
n1,
n2,
container
)
作用:
比较旧VNode和新VNode
决定如何更新
参数:
| 参数 | 含义 |
|---|---|
| n1 | 旧VNode |
| n2 | 新VNode |
| container | 容器 |
首次渲染:
patch(null,vnode,container)
更新:
patch(oldVNode,newVNode)
四、挂载元素
mountElement
首次创建:
<div>Hello</div>
流程:
VNode
↓
创建DOM
↓
设置属性
↓
设置children
↓
插入页面
代码思想:
const el =
document.createElement(vnode.type)
el.textContent =
vnode.children
container.appendChild(el)
五、处理Props
VNode:
{
type:'div',
props:{
id:'app',
class:'box'
}
}
遍历:
for(
const key in vnode.props
)
设置:
el.setAttribute(
key,
value
)
最终:
<div id="app"
class="box">
</div>
六、处理事件
Vue事件本质
例如:
<button @click="fn">
</button>
转换:
{
props:{
onClick:fn
}
}
判断:
if(key.startsWith('on'))
执行:
el.addEventListener(
event,
handler
)
七、更新元素
为什么需要更新
状态变化:
count++
生成:
新的VNode
需要比较:
旧VNode
VS
新VNode
流程:
oldVNode
|
▼
patch
|
▼
newVNode
八、更新Props
新旧props比较
旧:
{
id:'old'
}
新:
{
id:'new'
}
需要:
更新
流程:
遍历newProps
新增或变化
↓
设置
遍历oldProps
不存在于newProps
↓
删除
九、更新children
children有三种情况:
情况1
文本:
children:'hello'
情况2
数组:
children:[
vnode1,
vnode2
]
情况3
空:
children:null
十、子节点更新
旧文本,新数组
例如:
旧:
hello
新:
<div>
span
p
</div>
处理:
删除文本
挂载数组节点
旧数组,新文本
旧:
[
div,
span
]
新:
hello
处理:
删除旧节点
设置文本
数组更新数组
最复杂:
Diff算法
十一、Diff算法
为什么需要Diff
如果直接:
删除全部
重新创建
性能差。
目标:
复用已有DOM
十二、简单Diff
示例
旧:
[
A,
B,
C
]
新:
[
A,
C,
D
]
比较:
A 保留
B 删除
C 移动
D 新增
十三、key的作用
为什么需要key
例如:
<div v-for="item in list"
:key="item.id">
</div>
key用于:
唯一标识VNode
没有key:
Vue只能:
按位置比较
有key:
Vue可以:
找到对应节点
复用DOM
十四、简单Diff流程
第一步
寻找相同key:
old.key === new.key
第二步
patch更新:
复用节点
第三步
移动:
调整DOM位置
第四步
新增:
创建节点
第五步
删除:
移除旧节点
十五、最长递增子序列(LIS)
问题
移动节点时:
全部移动:
性能差。
Vue寻找:
不需要移动的节点
使用:
最长递增子序列
例如:
1 3 5
已经有序。
只移动:
其他节点
目的:
减少DOM移动次数。
十六、组件渲染基础
组件本质
组件:
{
render(){
}
}
组件VNode:
{
type:Component
}
处理:
type是对象
进入:
组件渲染流程
十七、组件挂载流程
创建组件实例
↓
执行setup
↓
生成render函数
↓
执行render
↓
得到VNode
↓
patch
十八、组件更新
数据变化:
响应式trigger
重新:
执行render
生成:
新的VNode
然后:
patch更新DOM
十九、渲染器整体流程
首次渲染
VNode
↓
patch
↓
mountElement
↓
DOM
更新渲染
数据变化
↓
effect
↓
render()
↓
newVNode
↓
patch(old,new)
↓
更新DOM
二十、渲染器核心代码结构
function createRenderer(){
function render(vnode,container){
patch(
null,
vnode,
container
)
}
function patch(n1,n2){
if(
typeof n2.type === 'string'
){
processElement()
}
}
function processElement(){
}
return {
render
}
}
第七章核心知识图谱
Renderer
render()
|
▼
patch()
|
├── Element
│
│ ├── mount
│ ├── patchProps
│ └── patchChildren
│
├── Component
│
│ ├── setup
│ ├── render
│ └── update
│
└── Diff
├── key
├── move
└── LIS
高频面试题
Renderer作用是什么?
负责:
VNode → DOM
patch函数作用?
比较:
旧VNode
新VNode
决定:
- 创建
- 更新
- 删除
为什么需要key?
帮助Diff:
- 找到节点
- 复用DOM
- 减少移动
Vue为什么使用虚拟DOM?
为了:
减少真实DOM操作
Diff算法核心?
比较新旧VNode:
复用
移动
新增
删除
为什么需要最长递增子序列?
减少DOM移动次数。
组件更新流程?
数据变化
↓
render重新执行
↓
生成新VNode
↓
patch
↓
更新DOM
第7章总结
Vue渲染器核心思想:
VNode
↓
patch
↓
DOM
更新过程:
响应式系统
数据变化
↓
触发render
↓
生成新VNode
↓
Diff
↓
更新DOM
Renderer主要解决:
- 如何创建DOM
- 如何更新DOM
- 如何删除DOM
- 如何比较新旧VNode
- 如何渲染组件
最终形成:
Reactivity
+
Renderer
+
Compiler
=
完整Vue运行时
第7章是理解 Vue3 runtime-core 源码的基础,也是面试中虚拟DOM、Diff算法、组件更新流程的核心章节。

浙公网安备 33010602011771号