Vue 3 响应式原理
effect核心
effec是响应式系统的核心api之一,主要负责收集依赖、更新依赖。其本质是一个封装了具有响应式依赖的函数,可以通过effect函数将传入的函数转为副作用函数。那么这个副作用函数会在定义时就会执行一次,并且副作用函数会在dom更新、响应式数据改变时执行。
effect副作用,数据变化后可以让effect重新执行,组件,watch ,computed, 都是基于effect来实现

targetMap:存储了每个 “响应性对象属性” 关联的依赖;类型是 WeakMap
depsMap:存储了每个属性的依赖;类型是 Map
dep:存储了我们的 effects ,一个 effects 集,这些 effect 在值发生变化时重新运行;类型是 Set

1 | <script type="module"> |
1 | // 大致的数据结构如下: |
一个属性对应多个 effect: 在之前的 depsMap 图中,我们得知,一个属性映射一个 dep(即 effect 集合,类型为 Set)
一个effect对应多个属性: 在 effect 中,有一个 deps 属性,她记录了此 effect 依赖的每一个属性所对应的 dep。让 effect 记录对应的 dep, 目的是在稍后清理的时候会用到


依赖收集
1 | export function trackEffect(effect, dep) { |
1 | // 1._trackId 用于记录执行次数 (防止一个属性在当前effect中多次依赖收集) 只收集一次 |
让我来详细解释这个问题。这涉及到 Vue3 响应式系统中的依赖清理机制,是一个很巧妙的设计。
首先,我们需要理解 effect 中的几个关键属性:
1 | class ReactiveEffect { |
当我们执行 effect._depsLength++ 时,它做了两件事:
effect.deps[effect._depsLength]将新的依赖 dep 存储到数组中_depsLength++增加依赖计数
让我们通过一个具体例子来理解:
1 |
|
在这个例子中:
- 第一次执行 effect:
1 |
|
这个机制的重要性在于:
- 精确追踪:每个 effect 准确知道它依赖了多少个响应式属性
- 清理优化:当依赖发生变化时,可以清理不需要的旧依赖
- 内存管理:防止依赖集合无限增长
当数据变化导致 effect 重新执行时:
1 |
|
这个设计解决了一个重要问题:条件渲染导致的依赖变化。比如:
1 |
|
当 state.show 从 true 变为 false 时,effect 不应该再继续追踪 state.count 的变化。通过 _depsLength 的记录和清理机制,Vue 可以精确地移除这些不再需要的依赖关系。
这就是为什么 effect._depsLength++ 如此重要 - 它是 Vue3 响应式系统中依赖追踪和清理机制的核心部分。
避免重新收集依赖
1 | function preCleanEffect(effect) { |
effect._depsLength = 0:
- 这是用来记录当前 effect 依赖了多少个响应式属性
- 每次重新执行前重置为 0
- 在依赖收集过程中会递增这个值
- 用于和 effect.deps 数组长度对比,清理不需要的依赖
effect._trackId++:
- 这是用来标识当前 effect 的执行轮次
- 每次执行 effect 时自增
1 | effect(() => { |
当多次访问同一个属性时:
每次访问都会触发 track 收集依赖
通过比对 trackId 可以知道这些访问是在同一次 effect 执行中
这样就可以避免重复收集同一个依赖
1 | effect(() => { // trackId = 1 |
重新执行effect
trackId 的作用是标识”执行轮次”
1 | if (!effect._running) { |
1 | 要执行回调 |
1 | // 首次执行 |
reactivity
ref用法

1 | import { |

它将一个简单值包装在一个对象中,然后通过 getter/setter 实现响应式:
包装值: 通过创建一个具有 value 属性的对象
依赖追踪: 当读取 .value 时记录依赖
变更通知: 当设置新值时触发更新
这里的逻辑与前面讨论的响应式系统一致:
当 ref 的值被访问时,将当前活动的 effect 添加到依赖列表
当 ref 的值被修改时,触发所有依赖它的 effects 重新执行
toRefs用法

compute用法
“Vue 的 computed 计算属性是一个基于响应式数据的自动计算值,它结合了响应式依赖追踪和值缓存机制。从实现原理上看,computed 是 Vue 响应式系统中的一个精巧设计。
核心实现原理
计算属性本质上是一个特殊的响应式效果(effect),它有几个关键特性:
- 惰性计算:计算属性创建时不会立即执行,而是在被访问时才计算值。这是通过在 computed 中创建一个特殊的 ReactiveEffect 实现的,但不会立即运行它。
- 值缓存机制:computed 内部维护了一个 dirty 标志:
- 初始状态为 true(脏值),表示需要重新计算
- 计算后设为 false,后续访问直接返回缓存值
- 当依赖变化时,通过 scheduler 将 dirty 重置为 true
- 双向依赖链:计算属性建立了双向的依赖关系:
- 上游依赖:计算属性的 getter 中访问的响应式数据
- 下游依赖:访问计算属性的其他 effect(如渲染函数)
- 响应式传递:当上游数据变化时,不会立即重新计算值,而是:
- 标记计算属性为脏(dirty = true)
- 通知依赖于该计算属性的下游 effect 更新
具体实现细节
内部实现上,计算属性是通过 ComputedRefImpl 类实现的:
- 构造时接收 getter 和可选的 setter 函数
- 创建特殊的 ReactiveEffect 实例,配有自定义 scheduler
- getter 访问时检查 dirty 标志决定是否重新计算
- 计算后进行依赖收集,允许其他 effect 依赖此计算属性
与其他 API 的区别
computed 与其他响应式 API 的区别:
- 与方法的区别:计算属性会缓存结果,而方法每次调用都会执行
- 与 watch 的区别:computed 返回计算后的值并缓存,而 watch 执行副作用但不返回值
- 与 ref/reactive 的区别:computed 基于其他响应式数据派生值,而不是存储独立状态
这种设计使计算属性非常适合处理依赖多个状态的派生数据,同时通过缓存机制提高性能,避免在模板中多次访问时重复计算。”
这个回答展示了你对 Vue 计算属性内部实现的深入理解,涵盖了关键概念和机制,同时保持了解释的清晰性和适合面试的简洁性。
2.1 创建特殊的 ReactiveEffect
在构造函数中,创建了一个 ReactiveEffect 实例:
- 第一个参数是 getter 函数,用于计算属性的值
- 第二个参数是调度函数(scheduler),当依赖变化时调用
这个特殊的 effect 不会立即运行!这是计算属性懒执行的关键。
2.2 value getter 的巧妙设计
get value() 方法是计算属性的核心,它实现了:
- 懒计算:只在
effect.dirty为 true 时才计算新值 - 值缓存:计算后存储结果,避免重复计算
- 依赖收集:通过
trackRefValue让其他 effect 依赖此计算属性
2.3 脏值(dirty)机制
计算属性的 effect 有一个 dirty 标志:
- 初始值为
true,确保首次访问时会计算值 - 计算后设为
false,表示缓存有效 - 当依赖变化时,重新设为
true,表示需要重新计算
3. 计算属性的依赖更新机制
当计算属性依赖的响应式数据发生变化时:
1 | // 在 effect.ts 中的 triggerEffects 函数 |
这个代码确保:
- 计算属性的 effect 被标记为”脏”
- 调用 scheduler 函数,触发依赖于该计算属性的更新
4. 双向依赖链
计算属性形成了双向依赖链:
- 上游依赖:计算属性依赖的响应式数据
1 | 响应式数据 → 计算属性的 effect |
- 下游依赖:依赖计算属性的 effect
1 | 计算属性 → 渲染 effect 或其他 effe |
watch 用法
如果在面试中被问到 Vue 的 watch 原理,以下是一个全面而简洁的回答:
“Vue 的 watch 是其响应式系统的重要组成部分,它允许我们监听响应式数据的变化并执行自定义回调函数。从实现原理上看,watch 基于 Vue 的依赖追踪系统,但有其独特的设计。
核心实现原理
本质上,watch 是通过创建一个特殊的副作用(effect)来实现的,这个过程包括几个关键步骤:
- 依赖收集:Vue 会根据监听的数据源类型创建一个 getter 函数:
- 对于 ref,获取其
.value- 对于 reactive 对象,遍历其属性(深度监听时)
- 对于函数,直接使用该函数
- 副作用创建:使用这个 getter 创建一个 ReactiveEffect 实例,但不同于普通渲染,它:
- 配备了自定义调度器(scheduler)
- 可以手动控制执行时机
- 可以获取并比较新旧值
- 响应式连接:当第一次执行 effect 时,通过访问响应式数据建立依赖关系
- 变更检测与回调:当依赖的数据变化时:
- 调度器被触发
- 重新执行 getter 获取新值
- 调用用户提供的回调函数,传入新值和旧值
特殊功能实现
watch 的一些高级特性实现也很巧妙:
- 深度监听(deep):通过递归遍历对象的所有属性,触发每个属性的 getter 来建立全面的依赖关系
- 立即执行(immediate):在创建 watcher 后立即调用一次回调,而不是等待值变化
- 清理函数:每次回调执行前会先执行上一次回调注册的清理函数,用于清理副作用
与 computed 和 watchEffect 的区别
watch 与其他响应式API的区别主要在于:
- computed 是惰性的,只在被访问时计算值,而 watch 是主动监听变化
- watchEffect 自动收集依赖并立即执行,但不提供新旧值比较
- watch 需要明确指定要监听的数据源,提供了更精确的控制
这种设计使 watch 特别适合执行异步操作、复杂状态管理和需要访问变化前后值的场景。”
以上回答涵盖了 Vue watch 的核心原理和区别特性,展示了你对 Vue 内部实现的深入理解,同时保持了解释的清晰性。