跳到主要内容

Vue 3 响应式源码深入

响应式系统源码解析

1. 核心数据结构

// 依赖收集的核心映射
type Dep = Set<ReactiveEffect> // 订阅该属性的 effect 集合
const targetMap = new WeakMap<object, Map<string, Dep>>()

// WeakMap<target, Map<key, Set<effect>>>
// WeakMap 保证 target 被 GC 时自动清理

2. reactive / ref 区别

reactiveref
接受类型对象/数组任意值
访问方式直接访问属性.value
实现Proxygetter/setter(对象内部用 reactive)
解构会丢失响应式toRefs() 保持响应式

3. track / trigger 流程

【读取属性】
get trap → track(target, key)
→ 找到当前 activeEffect
→ targetMap[target][key].add(activeEffect)

【修改属性】
set trap → trigger(target, key)
→ 取出 targetMap[target][key]
→ 遍历执行所有 effect

4. effect / computed / watch 关系

  • ReactiveEffect 是核心类,computed/watch 都基于它
  • computed:懒执行,有 _dirty 标记,依赖变化时只标记脏,访问时才重算
  • watch:立即执行 effect,对比新旧值后调用回调
  • watchEffect:直接执行 effect,自动收集内部响应式依赖

5. 响应式边界问题

// ❌ 解构丢失响应式
const { count } = reactive({ count: 0 })

// ✅ 用 toRefs
const state = reactive({ count: 0 })
const { count } = toRefs(state)
count.value++ // 仍然响应式

// ❌ 将响应式对象赋值给普通变量
let user = reactive({ name: 'A' })
user = { name: 'B' } // 丢失响应式!

// ✅ 修改属性而非替换对象
user.name = 'B'

6. shallowReactive / readonly / shallowReadonly

  • shallowReactive:只代理第一层,深层不响应
  • readonly:深层只读,set 时警告
  • markRaw:标记对象不被转为响应式(如第三方实例)

7. 高频面试题

  • Vue 3 为什么用 Proxy 替换 Object.defineProperty?
    • 支持新增/删除属性、数组下标修改
    • 懒代理嵌套对象(访问时才代理),性能更好
    • 支持 Map/Set/WeakMap/WeakSet
  • ref 内部为什么对对象用 reactive? 保持深层响应式
  • computed 为什么是懒计算? 避免不被读取时白白计算
  • watchEffect 和 watch 的 flush 时机? 默认 pre(DOM 更新前),post 在 DOM 更新后