vue3响应式总结

在 Vue3 的响应式系统中,所有与响应式数据关联的副作用函数确实会被存储在 targetMap 结构中,它们的依赖关系建立遵循统一的机制。以下是具体分类说明:


核心概念关系图

            全局 WeakMap
          ┌───────────┐
          │ targetMap │
          └─────┬─────┘
                │
      Key: 响应式对象 (target) 
      Value: depsMap (Map 结构)
                │
                ├── Key: 属性键 (如 'count')
                │    │
                │    └── Value: dep (Set 集合)
                │           │
                │           └── 收集所有依赖该属性的 effect
                │
                └── Key: 另一个属性键 (如 'message')
                     │
                     └── Value: 对应 dep 集合

所有会触发依赖收集的情况

1. effect()

import { effect, reactive } from 'vue'
const state = reactive({ count: 0 })

effect(() => {
  console.log(state.count) // 触发 track,收集到 targetMap[state]['count']
})

2. watch()

watch(
  () => state.count, // getter 函数访问响应式属性
  (newVal) => { /* ... */ }
)
// 内部创建 effect,会被收集到 targetMap[state]['count']

3. watchEffect()

watchEffect(() => {
  console.log(state.count) // 直接访问,立刻触发收集
})
// 内部就是一个自动运行的 effect

4. computed()

const double = computed(() => state.count * 2)
// 计算属性的 getter 会被包裹成 effect,访问 state.count 时被收集

5. 模板渲染(最重要但最隐蔽)

<template>
  <div>{{ state.count }}</div> <!-- 编译后的渲染函数访问 state.count -->
</template>
  • 每个组件实例都有一个渲染 effect,当模板中访问响应式数据时,会触发依赖收集
  • 等效于 effect(() => renderComponent())

6. 其他

  • 组件的 setup() 函数中直接访问响应式数据
  • 生命周期钩子中访问响应式数据(若发生在响应式上下文中)

响应式系统完整流程

  1. 访问数据:当副作用函数(effect、watch、模板等)读取响应式对象的属性时
  2. 触发 track():内部通过 Proxy 的 get 拦截器调用 track 函数
  3. 追踪依赖:将当前活跃的 ReactiveEffect 实例添加到对应属性的 dep 集合
  4. 变化触发:当修改属性时,触发 trigger() 并找到相关的 dep 集合,遍历执行其中的副作用

关键验证方式

可以通过调试工具观察 targetMap 的结构:

// 在浏览器中临时暴露 targetMap(仅调试用)
window.__VUE__ = { targetMap }

然后访问响应式数据,观察对应属性下 dep 集合中的 effect 数量。


特殊说明

  • 自动解绑机制:组件卸载时,其对应的 effect 会自动从 dep 中被移除,避免内存泄漏
  • 嵌套 effect:使用 effectScope 管理的 effect 会形成层级关系,便于批量处理

所有通过 Vue3 响应式 API 建立的副作用都会被纳入 targetMap 的统一管理,这也是 Vue3 实现精确更新的核心机制。

在 Vue3 的响应式系统中,targetMap 是核心依赖关系存储结构,通过结合源码中的具体实现,我们可以深入理解不同场景下副作用函数的收集机制。以下是逐层分解的源码级解析:


关键源码结构

1. 核心数据结构定义(effect.ts

// 响应式系统的全局存储结构
type Dep = Set<ReactiveEffect>
type KeyToDepMap = Map<any, Dep>
export const targetMap = new WeakMap<any, KeyToDepMap>()

// 当前活跃的 effect 栈
let activeEffect: ReactiveEffect | undefined
const effectStack: ReactiveEffect[] = []

二、副作用函数追踪全流程

1. 副作用封装:ReactiveEffect 类(源码核心)

// packages/reactivity/src/effect.ts
export class ReactiveEffect<T = any> {
  deps: Dep[] = [] // 记录所有依赖本 effect 的 dep 集合

  constructor(
    public fn: () => T,       // 副作用函数本体
    public scheduler?: () => void // 调度器(用于 watch/computed)
  ) {}

  run() {
    if (!effectStack.includes(this)) { // 防止同一个 effect 重复入栈
      try {
        effectStack.push((activeEffect = this)) // 压栈并设置 activeEffect
        return this.fn() // 执行副作用函数,触发 track
      } finally {
        effectStack.pop() // 出栈
        activeEffect = effectStack[effectStack.length - 1] // 恢复之前的 effect
      }
    }
  }

  stop() { /* 从所有 dep 中移除本 effect */}
}

关键生命周期:当 effect.run() 执行时,activeEffect 指向当前实例,函数内部的响应式数据访问会触发依赖收集。


2. 依赖收集:track() 函数

export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (activeEffect === undefined) return // 没有活跃 effect 时直接返回

  // 获取或创建 target 对应的 depsMap
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }

  // 获取或创建 key 对应的 dep 集合
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }

  // 建立 effect 与 dep 的双向关联
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect)
    activeEffect.deps.push(dep) // effect 也记录 dep
  }
}

触发时机:当通过响应式 Proxy 的 get 拦截器访问属性时,调用 track(target, 'get', key)


三、具体场景的源代码实现

1. 基础 effect() 函数

export function effect<T = any>(
  fn: () => T,
  options?: ReactiveEffectOptions
): ReactiveEffectRunner {
  const _effect = new ReactiveEffect(fn) // 创建 ReactiveEffect 实例
  _effect.run() // 立即运行,触发 track
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  runner.effect = _effect // 关联 runner 与 effect
  return runner
}

2. 计算属性 computed()(computed.ts)

export function computed<T>(
  getter: () => T
): ComputedRef<T> {
  const _effect = new ReactiveEffect(getter, () => {
    // 调度器:标记为脏数据,触发依赖更新(用 Promise.resolve 异步更新)
    trigger(computed, TriggerOpTypes.SET, 'value')
  })

  const computed = {
    get value() {
      track(computed, TrackOpTypes.GET, 'value') // 收集计算属性自身的依赖
      return _effect.run() // 执行 getter,收集内部依赖
    }
  } as ComputedRef<T>

  return computed
}

3. watch()watchEffect()(apiWatch.ts)

// watch 的核心实现简写
function doWatch(
  source: WatchSource,
  cb: WatchCallback,
): StopHandle {
  const getter = () => source() // 包装为 getter 函数

  const scheduler = () => {
    const newValue = effect.run() // 重新计算值
    cb(newValue, oldValue) // 触发回调
  }

  const effect = new ReactiveEffect(getter, scheduler)
  effect.run() // 首次运行收集依赖

  return () => effect.stop()
}

// watchEffect 是简化版 watch
function watchEffect(effectFn: () => void) {
  return doWatch(effectFn, null)
}

4. 模板渲染的 effect(renderer.ts)

// 组件实例初始化时创建渲染 effect
function setupRenderEffect(
  instance: ComponentInternalInstance,
) {
  const componentUpdateFn = () => {
    // 生成子树 vnode(会访问响应式数据)
    const subTree = render.call(instance.proxy)
    patch(instance.subTree, subTree) // 对比更新
    instance.subTree = subTree
  }

  // 创建渲染 effect(关键!)
  instance.update = new ReactiveEffect(componentUpdateFn, () => {
    queueJob(instance.update!) // 调度器:将更新加入异步队列
  })

  // 首次执行收集依赖
  instance.update.run()
}

四、依赖关系卸载机制

当组件卸载或手动停止 effect 时调用 ReactiveEffect.stop():

class ReactiveEffect {
  stop() {
    for (const dep of this.deps) {
      dep.delete(this) // 从所有依赖的 dep 集合中删除自身
    }
    this.deps.length = 0 // 清空 deps 数组
  }
}

五、验证案例:通过 Effect 源码输出依赖

调试代码示例:

import { reactive, effect, watch } from 'vue'

const state = reactive({ count: 0, msg: 'hello' })
window.__VUE__ = { targetMap } // 暴露到全局

effect(() => { console.log('effect:', state.count) })
watch(() => state.msg, (val) => { console.log('watch:', val) })

在浏览器控制台中输入以下命令观察结构:

// 得到 state 对应的 depsMap
const depsMap = __VUE__.targetMap.get(state)

// 查看 'count' 属性收集的 effect
console.log(depsMap.get('count')) // 输出的 Set 中包含 ReactiveEffect 实例

// 检查 effect 的 fn 属性
Array.from(depsMap.get('count')).map(e => e.fn.toString())
// 可看到具体副作用函数代码

六、重点设计启示

  1. 统一依赖收集入口
    所有副作用最终通过 activeEffect 暂存当前上下文,在 track() 阶段集中处理,实现了 API 的统一管理。

  2. 层级化 effect 栈
    effectStack 实现嵌套 effect 的正确上下文切换(常见于组件树渲染)。

  3. 调度器分离
    ReactiveEffect.scheduler 允许响应式更新与副作用的实际执行解耦,例如:

    • 组件渲染使用队列进行批处理更新
    • watchflush: 'post' 选项延迟回调到 DOM 更新后
  4. 惰性计算优化
    computed 属性通过 dirty 标志和响应式值的缓存,避免重复计算。

通过这样的源码设计,Vue3 在不同场景下均能高效维护响应式依赖关系,而 targetMap 如同中枢神经将各部分有机连接。

posted @   木燃不歇  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· 单线程的Redis速度为什么快?
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
点击右上角即可分享
微信分享提示