《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主要解决:

  1. 如何创建DOM
  2. 如何更新DOM
  3. 如何删除DOM
  4. 如何比较新旧VNode
  5. 如何渲染组件

最终形成:

Reactivity
        +
Renderer
        +
Compiler

=

完整Vue运行时

第7章是理解 Vue3 runtime-core 源码的基础,也是面试中虚拟DOM、Diff算法、组件更新流程的核心章节。

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