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 区别
| reactive | ref | |
|---|---|---|
| 接受类型 | 对象/数组 | 任意值 |
| 访问方式 | 直接访问属性 | .value |
| 实现 | Proxy | getter/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 更新后