Vue响应式和Diff

Vue 的响应式系统(Reactivity)和 Diff 算法解决的是两个不同的问题。

  • 响应式系统: 知道数据什么时候发生变化,以及哪些组件需要更新。
  • Diff 算法: 在组件更新时,判断新旧虚拟 DOM 有哪些区别,从而尽量减少真实 DOM 操作。

一、先看一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
count.value++
}
</script>

<template>
<div>
<h1>Vue 学习</h1>
<p>当前计数:{{ count }}</p>
<button @click="increment">+1</button>
</div>
</template>

当你点击按钮时:

1
count.value++

响应式系统发现 count 改变了,于是通知依赖它的组件需要更新。

但是这里有一个问题:

响应式系统知道 count 变化了,不代表它直接知道真实 DOM 中具体哪个文本节点需要修改。

因此 Vue 会重新执行组件的渲染逻辑,生成新的虚拟 DOM,并通过更新算法处理变化。

二、响应式和 Diff 的分工

Vue 3 组件更新流程(简化示意)

Vue3组件更新流程

假设更新前后的虚拟 DOM 分别是:

旧虚拟 DOM:

1
2
3
4
5
<div>
<h1>Vue 学习</h1>
<p>当前计数:0</p>
<button>+1</button>
</div>

新虚拟 DOM:

1
2
3
4
5
<div>
<h1>Vue 学习</h1>
<p>当前计数:1</p>
<button>+1</button>
</div>

Diff 比较后发现:

元素 是否变化 操作
div 否 保留
h1 否 保留
p 是 更新文本
button 否 保留

最终只需要更新计数文本,不必重新创建整个 DOM。

三、既然知道 count 变化了,为什么不直接更新 p?

这才是问题的核心。

实际上,Vue 3 已经在尽可能这样做了。

Vue 3 不只是依靠传统的虚拟 DOM Diff,还使用了编译时优化。

例如:

1
2
3
4
5
6
<template>
<div>
<h1>Vue 学习</h1>
<p>{{ count }}</p>
</div>
</template>

Vue 编译器可以分析出:

  • <h1> 是静态内容。
  • <p> 包含动态文本。
  • 只有 <p> 中的文本需要关注变化。

Vue 3 使用 Patch Flags(补丁标记)、静态提升等技术,让更新过程尽可能跳过静态部分。

所以 Vue 3 并不是每次都完整比较整棵虚拟 DOM 树。

但为什么还需要 Diff?

因为组件可能存在复杂的结构变化:

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div>
<p v-if="isLogin">欢迎回来</p>
<button v-else>请登录</button>

<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
</div>
</template>

这里不仅仅是文本发生变化,还可能出现:

  • p 被 button 替换。
  • 列表新增元素。
  • 列表删除元素。
  • 列表元素顺序变化。

这些场景需要 Vue 判断如何复用、移动、创建或删除 DOM 节点。

响应式系统本身不负责这些结构上的更新决策。

四、总结

对比 响应式系统 Diff 算法
解决问题 什么时候需要更新 更新哪些 DOM
关注对象 响应式数据和依赖 新旧虚拟 DOM
核心技术 Proxy、依赖追踪、effect 节点比较、Patch
主要作用 触发更新 尽量减少 DOM 操作

记住一句话:

响应式系统负责发现变化,Diff 算法负责处理渲染结果的差异,而 Vue 3 的编译器负责帮助它们减少不必要的工作。

Vue 3 为什么使用虚拟 DOM,而 SolidJS 等框架可以使用细粒度响应式直接更新 DOM,几乎不需要虚拟 DOM Diff?

这正好能进一步解释这个问题。


Vue响应式和Diff
https://umeshu.cn/2021/09/13/Front/Vue响应式和Diff/
作者
IchiharaYuuko
发布于
2021年9月13日
许可协议