Vue3 的响应式系统依赖收集与触发更新原理
字数 682 2025-11-05 23:47:54
Vue3 的响应式系统依赖收集与触发更新原理
题目描述
理解Vue3响应式系统中依赖收集(track)和触发更新(trigger)的核心机制,包括如何建立响应式数据与副作用函数之间的关联,以及数据变化时如何精确通知相关依赖。
知识讲解
1. 响应式系统的核心目标
- 当响应式数据发生变化时,自动执行依赖于该数据的副作用函数(如组件的渲染函数、计算属性、watch监听器等)
- 需要建立数据属性与副作用函数之间的映射关系,实现精确更新
2. 基本概念定义
副作用函数(Effect)
// 任何读取响应式数据的函数都是副作用函数
const effect = () => {
document.body.innerText = obj.text // 读取obj.text
}
依赖关系(Deps Map)
- 采用三级映射结构:
TargetMap: WeakMap,键是响应式对象,值是DepsMapDepsMap: Map,键是对象属性名,值是Dep SetDep: Set,存储所有依赖于该属性的副作用函数
3. 依赖收集(Track)过程
步骤1:建立数据结构
const targetMap = new WeakMap() // 全局依赖存储
function track(target, key) {
if (!activeEffect) return // 没有活跃的副作用函数则不收集
// 第一级:找到或创建target对应的depsMap
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
// 第二级:找到或创建key对应的dep集合
let dep = depsMap.get(key)
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
// 第三级:将当前活跃的副作用函数添加到dep中
trackEffects(dep)
}
function trackEffects(dep) {
if (dep.has(activeEffect)) return // 避免重复收集
dep.add(activeEffect)
// 同时,副作用函数也需要记录自己属于哪些dep(用于清理)
activeEffect.deps.push(dep)
}
步骤2:代理getter拦截
function createGetter() {
return function get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
// 依赖收集
track(target, key)
if (isObject(res)) {
return reactive(res) // 深层响应式
}
return res
}
}
4. 触发更新(Trigger)过程
步骤1:查找依赖并执行
function trigger(target, key) {
// 从targetMap中找到target对应的depsMap
const depsMap = targetMap.get(target)
if (!depsMap) return // 没有依赖直接返回
// 找到key对应的dep集合
const dep = depsMap.get(key)
if (dep) {
triggerEffects(dep)
}
}
function triggerEffects(dep) {
// 避免在执行effect时又触发收集,造成无限循环
const effects = new Set(dep)
effects.forEach(effect => {
// 如果effect有调度器,则通过调度器执行(用于批量更新等优化)
if (effect.scheduler) {
effect.scheduler()
} else {
effect() // 直接执行副作用函数
}
})
}
步骤2:代理setter拦截
function createSetter() {
return function set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
// 触发更新(只有值真正改变时才触发)
if (hasChanged(value, oldValue)) {
trigger(target, key)
}
return result
}
}
5. 完整的响应式函数示例
let activeEffect = null
class ReactiveEffect {
constructor(fn, scheduler) {
this.fn = fn
this.scheduler = scheduler
this.deps = [] // 记录该effect被哪些dep收集
}
run() {
activeEffect = this
const result = this.fn()
activeEffect = null
return result
}
}
function effect(fn, options = {}) {
const _effect = new ReactiveEffect(fn, options.scheduler)
_effect.run()
// 返回runner函数,可以手动执行
const runner = _effect.run.bind(_effect)
runner.effect = _effect
return runner
}
6. 依赖清理机制
为什么需要清理:
- 副作用函数可能不再依赖某些数据,需要从对应的dep中移除
- 避免内存泄漏和无效更新
清理实现:
class ReactiveEffect {
constructor(fn, scheduler) {
// ...其他代码
this.deps = []
}
run() {
// 先清理之前的依赖
cleanupEffect(this)
activeEffect = this
const result = this.fn()
activeEffect = null
return result
}
}
function cleanupEffect(effect) {
const { deps } = effect
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect) // 从所有dep中移除该effect
}
deps.length = 0 // 清空deps数组
}
}
7. 实际应用示例
const obj = reactive({ count: 0, name: 'vue' })
// 副作用函数1:只依赖count
effect(() => {
console.log('count changed:', obj.count)
})
// 副作用函数2:依赖count和name
effect(() => {
console.log('both changed:', obj.count, obj.name)
})
obj.count++ // 触发两个effect
obj.name = 'vue3' // 只触发第二个effect
总结
Vue3的依赖收集与触发更新机制通过三级映射结构建立精确的依赖关系,在get操作时收集依赖,在set操作时触发更新。通过effect跟踪当前活跃的副作用函数,结合WeakMap、Map、Set的数据结构,实现了高效的响应式更新。清理机制确保依赖关系的准确性,避免内存泄漏。