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,键是响应式对象,值是DepsMap
    • DepsMap: Map,键是对象属性名,值是Dep Set
    • Dep: 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的数据结构,实现了高效的响应式更新。清理机制确保依赖关系的准确性,避免内存泄漏。

Vue3 的响应式系统依赖收集与触发更新原理 题目描述 理解Vue3响应式系统中依赖收集(track)和触发更新(trigger)的核心机制,包括如何建立响应式数据与副作用函数之间的关联,以及数据变化时如何精确通知相关依赖。 知识讲解 1. 响应式系统的核心目标 当响应式数据发生变化时,自动执行依赖于该数据的副作用函数(如组件的渲染函数、计算属性、watch监听器等) 需要建立数据属性与副作用函数之间的映射关系,实现精确更新 2. 基本概念定义 副作用函数(Effect) 依赖关系(Deps Map) 采用三级映射结构: TargetMap : WeakMap,键是响应式对象,值是DepsMap DepsMap : Map,键是对象属性名,值是Dep Set Dep : Set,存储所有依赖于该属性的副作用函数 3. 依赖收集(Track)过程 步骤1:建立数据结构 步骤2:代理getter拦截 4. 触发更新(Trigger)过程 步骤1:查找依赖并执行 步骤2:代理setter拦截 5. 完整的响应式函数示例 6. 依赖清理机制 为什么需要清理: 副作用函数可能不再依赖某些数据,需要从对应的dep中移除 避免内存泄漏和无效更新 清理实现: 7. 实际应用示例 总结 Vue3的依赖收集与触发更新机制通过三级映射结构建立精确的依赖关系,在get操作时收集依赖,在set操作时触发更新。通过effect跟踪当前活跃的副作用函数,结合WeakMap、Map、Set的数据结构,实现了高效的响应式更新。清理机制确保依赖关系的准确性,避免内存泄漏。