《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

核心能力:

  1. mountElement
  2. patchElement
  3. patchProps
  4. patchChildren
  5. unmount
  6. Event Invoker

这些能力构成 Vue Renderer 的基础。

下一章开始正式进入:

Diff算法

即:

Array Children
↓
如何高效更新

这是 Vue3 性能优化最核心的部分。

posted @ 2025-04-28 15:28  Li_pk  阅读(5)  评论(0)    收藏  举报