# Vue3 响应式原理(Proxy/Effect/依赖收集)
30秒提纲:响应式基于 Proxy 拦截 get/set,
effect在 get 时通过track收集依赖,在 set 时通过trigger触发副作用;依赖桶数据结构为WeakMap -> Map -> Set。scheduler支持去抖/合并更新;ref负责原始值,reactive负责对象,computed基于惰性effect与脏标记。
# 1. 核心概念
- reactive/ref/shallowReactive/shallowRef
- effect/track/trigger,依赖桶(WeakMap → Map → Set)
- scheduler、stop、onStop
- computed 的惰性求值与脏标记
# 2. 依赖桶数据结构
interface DepMap extends Map<PropertyKey, Set<ReactiveEffect>> {}
const bucket: WeakMap<object, DepMap> = new WeakMap()
- WeakMap 按 target 分桶,避免内存泄漏
- 每个 key 对应一个 Set /effect>,去重触发
# 3. track / trigger 伪代码
let activeEffect: ReactiveEffect | null = null
export function effect(fn: Function, options: { scheduler?: Function } = {}) {
const runner: any = () => {
try { activeEffect = runner; cleanup(runner); return fn() } finally { activeEffect = null }
}
runner.deps = []
runner.scheduler = options.scheduler
runner()
return runner
}
function track(target: object, key: PropertyKey) {
if (!activeEffect) return
let depMap = bucket.get(target)
if (!depMap) bucket.set(target, (depMap = new Map()))
let dep = depMap.get(key)
if (!dep) depMap.set(key, (dep = new Set()))
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
function trigger(target: object, key: PropertyKey) {
const depMap = bucket.get(target); if (!depMap) return
const effects = depMap.get(key); if (!effects) return
effects.forEach((e: any) => (e.scheduler ? e.scheduler(e) : e()))
}
function cleanup(runner: any) { runner.deps.forEach((dep: Set<any>) => dep.delete(runner)); runner.deps.length = 0 }
# 4. reactive 与 ref 的实现要点
- reactive:对象 Proxy,拦截 get/set → track/trigger
- ref:包装原始值
{ value },对.value做 track/trigger;在模板中会被“自动解包”
# 5. computed 实现(惰性 + 脏标记)
function computed(getter: () => any){
let dirty = true, value: any
const runner = effect(getter, { scheduler(){ dirty = true } })
return {
get value(){ if (dirty){ value = runner(); dirty = false } return value }
}
}
# 6. scheduler 与批量更新
- 利用
scheduler将多次触发合并到微任务队列,避免重复执行
const queue = new Set<Function>()
const flush = Promise.resolve()
function queueJob(job: Function){ queue.add(job); flush.then(()=>{ queue.forEach(j=>j()); queue.clear() }) }
# 7. 边界与常见坑
- 解构导致丢失响应性:使用
toRefs/storeToRefs markRaw/toRaw:避免/获取代理shallowReactive/shallowRef:仅一层- Map/Set 的触发类型:
set/add/delete需要自定义依赖键 - 数组 length/索引写入的特殊触发
# 8. watch 与 watchEffect 的关系
watchEffect立即收集依赖;watch基于 getter 对比新旧值- 清理函数、flush(post/pre/sync) 与竞态取消
# 9. 性能建议 Checklist
- 合理切分响应式对象,避免大对象深层响应化
- 使用
shallowRef+ 手动.value = {...}合并替换,降低深层依赖 - 频繁变更使用
scheduler合并 - 稳定 key,避免重复挂载
# 10. 高频问法(提纲)
- Vue3 如何进行依赖收集?为什么用 WeakMap?
- computed 为何是惰性的?如何基于 effect 实现?
- 为什么解构会丢响应性?如何修复?
- watch 与 watchEffect 区别与清理时机?
- ref 与 reactive 在模板中的自动解包规则?