《Vue.js设计与实现》笔记 第8章:挂载与更新
Vue.js设计与实现 第8章:挂载与更新
本章导读
第7章完成了渲染器总体设计:
VNode
↓
patch
↓
DOM
但真正的渲染器还缺少:
- 元素挂载
- 属性处理
- 事件处理
- 子节点更新
- DOM复用
第8章将实现:
Element节点
创建
↓
挂载
↓
更新
↓
卸载
这是 Vue Renderer 的核心基础。
一、渲染器工作流程
整体流程:
render()
↓
patch()
↓
Element ?
Component ?
↓
挂载或更新
↓
真实DOM
render函数
renderer.render(
vnode,
container
)
作用:
将VNode渲染到容器
首次渲染
render(vnode,container)
此时:
container._vnode === null
执行:
patch(
null,
vnode,
container
)
更新渲染
再次:
render(newVNode)
执行:
patch(
oldVNode,
newVNode
)
二、patch函数
作用
Vue渲染器核心入口:
patch(
n1,
n2,
container
)
参数:
| 参数 | 说明 |
|---|---|
| n1 | 旧VNode |
| n2 | 新VNode |
| container | 容器 |
判断节点类型
typeof vnode.type
例如:
'div'
表示:
原生元素
例如:
Component
表示:
组件
三、挂载元素
mountElement
首次渲染:
patch(
null,
vnode,
container
)
进入:
mountElement()
创建元素
VNode:
{
type:'div'
}
创建:
const el =
createElement(
vnode.type
)
结果:
<div></div>
四、处理子节点
children:
有三种情况。
文本节点
children:'hello'
处理:
setElementText(
el,
vnode.children
)
结果:
<div>hello</div>
数组节点
children:[
vnode1,
vnode2
]
遍历:
children.forEach(child=>{
patch(
null,
child,
el
)
})
结果:
<div>
...
</div>
空节点
children:null
无需处理。
五、处理Props
VNode:
{
type:'div',
props:{
id:'app',
class:'box'
}
}
遍历:
for(
const key
in vnode.props
)
调用:
patchProps(
el,
key,
null,
value
)
统一处理属性。
六、DOM Properties与Attributes
Attribute
例如:
<input value="hello">
通过:
setAttribute()
设置。
DOM Property
例如:
input.value='hello'
通过:
el[key] = value
设置。
Vue如何选择
判断:
key in el
成立:
el[key]=value
否则:
setAttribute()
七、class处理
为什么特殊处理
最常用:
class
Vue直接:
el.className = value
而不是:
setAttribute(
'class'
)
原因:
性能更好
八、事件处理
事件属性
例如:
{
onClick:handler
}
判断:
/^on/.test(key)
得到:
click
绑定:
addEventListener(
'click',
handler
)
九、事件更新问题
问题
旧:
onClick = fn1
新:
onClick = fn2
如果:
remove
+
add
频繁操作DOM。
性能差。
十、事件缓存Invoker
Vue方案:
invoker
结构:
const invoker = {
value:handler
}
事件绑定:
el.addEventListener(
'click',
invoker
)
更新:
invoker.value = newHandler
无需重新绑定。
工作流程
DOM事件
↓
Invoker
↓
invoker.value
↓
真正回调
十一、事件冒泡问题
问题
事件执行期间:
新增事件
可能立即触发。
解决:
记录时间:
performance.now()
比较:
事件触发时间
>
绑定时间
才执行。
避免异常触发。
十二、更新元素
patchElement
旧:
<div id="old">
新:
<div id="new">
进入:
patchElement()
流程:
更新Props
↓
更新Children
十三、更新Props
第一步
遍历:
newProps
变化:
新增
修改
执行:
patchProps()
第二步
遍历:
oldProps
不存在:
删除
执行:
patchProps(
key,
old,
null
)
十四、更新Children
情况1
新:
文本
旧:
文本
直接:
setText()
情况2
旧:
Array
新:
Text
流程:
卸载旧节点
↓
设置文本
情况3
旧:
Text
新:
Array
流程:
清空文本
↓
挂载数组节点
情况4
旧:
Array
新:
Array
进入:
Diff算法
后续章节重点。
十五、卸载节点
为什么需要卸载
例如:
旧:
[
A,
B,
C
]
新:
[
A
]
需要删除:
B
C
unmount
实现:
function unmount(vnode){
const parent =
vnode.el.parentNode
if(parent){
parent.removeChild(
vnode.el
)
}
}
十六、节点类型变化
示例
旧:
{
type:'div'
}
新:
{
type:'p'
}
无法复用。
处理:
卸载旧节点
↓
重新挂载
十七、DOM复用
相同类型
旧:
<div>
新:
<div>
可以:
复用DOM
实现:
n2.el = n1.el
这样:
无需重新创建DOM
十八、渲染器平台无关设计
为什么使用配置项
浏览器:
document.createElement()
小程序:
createView()
Canvas:
drawRect()
因此:
createRenderer(options)
传入:
createElement
insert
setText
patchProps
实现:
平台无关
十九、挂载与更新整体流程
首次渲染:
render
↓
patch
↓
mountElement
↓
DOM
更新:
render
↓
patch
↓
patchElement
↓
patchChildren
↓
DOM更新
第二十章核心代码结构
function patch(
n1,
n2,
container
){
if(!n1){
mountElement()
}else{
patchElement()
}
}
第8章核心知识图谱
Renderer
render()
│
▼
patch()
│
├── mountElement
│
│ ├── createElement
│ ├── props
│ ├── children
│ └── insert
│
├── patchElement
│
│ ├── patchProps
│ └── patchChildren
│
├── unmount
│
└── DOM复用
Props
├── class
├── style
├── attribute
├── property
└── event
Event
└── Invoker缓存
高频面试题
patch作用是什么?
比较:
旧VNode
新VNode
决定:
- 挂载
- 更新
- 卸载
为什么需要patchProps?
统一处理:
class
style
事件
属性
Property与Attribute区别?
Property
↓
DOM对象属性
Attribute
↓
HTML属性
Vue为什么缓存事件?
避免:
removeEventListener
addEventListener
频繁执行。
为什么复用DOM?
减少:
创建DOM
删除DOM
开销。
什么时候不能复用DOM?
节点类型变化
例如:
div → p
本章总结
第8章实现了:
Element节点
创建
更新
删除
复用
渲染流程:
VNode
↓
patch
↓
mountElement
or
patchElement
↓
DOM
核心能力:
- mountElement
- patchElement
- patchProps
- patchChildren
- unmount
- Event Invoker
这些能力构成 Vue Renderer 的基础。
下一章开始正式进入:
Diff算法
即:
Array Children
↓
如何高效更新
这是 Vue3 性能优化最核心的部分。

浙公网安备 33010602011771号