vue 3 effect作用与原理

Vue 3 的 Effect(副作用) 是整个响应式系统的核心机制,负责管理依赖追踪和响应式触发。理解其作用和原理对掌握 Vue 的底层机制至关重要。


一、核心作用

1. 依赖追踪(Dependency Tracking)

  • 自动跟踪响应式数据在副作用函数中的使用。
  • 示例代码
    import { reactive, effect } from 'vue'
    
    const obj = reactive({ count: 0 })
    
    effect(() => {
      console.log(`count is: ${obj.count}`)
    })
    
    • 当首次执行 effect 时,函数 () => console.log(...) 会被运行。
    • 触发 obj.countget 操作,触发依赖收集(将当前 effect 关联到 obj.count)。

2. 自动响应(Automatic Re-run)

  • 当响应式数据的依赖变化时,自动重新执行副作用函数:
    obj.count++  // 触发依赖更新,控制台打印 "count is: 1"
    

3. 支撑高级 API

  • computedwatch、组件渲染函数等底层都依赖于 effect 实现。

二、实现原理

1. 核心类:ReactiveEffect

Vue 3 用 ReactiveEffect 类封装副作用逻辑,简化后的源码结构如下:

class ReactiveEffect<T = any> {
  // 当前 effect 的所有依赖项(其他响应式对象)
  deps: Dep[] = []

  // 构造函数参数
  constructor(
    public fn: () => T,            // 副作用函数
    public scheduler?: () => void  // 调度函数(控制重新执行方式)
  ) {}

  // 运行副作用(触发依赖收集)
  run() {
    activeEffect = this // 标记当前正在运行的 effect
    try {
      return this.fn()
    } finally {
      activeEffect = undefined
    }
  }

  // 停止侦听
  stop() { /* 从所有依赖中移除自身 */ }
}

2. 依赖收集流程(Track)

  • 数据结构
    type Dep = Set<ReactiveEffect>    // 依赖集合
    type TargetMap = WeakMap<Object, Map<string, Dep>> // 全局依赖存储
    
  • 触发时机:响应式数据的 get 操作触发时。
  • 流程
    1. 根据响应式对象 (target) 和键 (key) 找到存入 targetMap 的依赖集合 (dep)。
    2. 将当前活跃的 activeEffect 添加到 dep 中。
    3. 同时将 dep 加入 activeEffect.deps(反向记录,用于 cleanup)。

3. 触发更新(Trigger)

  • 触发时机:响应式数据的 set 操作时。
  • 流程
    1. 根据 targetkeytargetMap 获取对应的 dep 集合。
    2. 遍历 dep 中所有 effect
    • 如果有 scheduler(如 computed),执行调度器(优化性能)。
    • 否则直接执行 effect.run()

4. 调度器(Scheduler)

  • 允许控制 effect 如何重新执行:
    effect(() => {
      console.log(obj.count)
    }, {
      scheduler(effect) { 
        // 如将 effect 推入微任务队列中异步执行
        queueMicrotask(effect.run) 
      }
    })
    
  • 应用场景:
    • watch 的异步批处理更新。
    • computed 的值懒更新。

三、关键优化设计

1. 嵌套 Effect 栈

  • 用栈结构 effectStack 跟踪嵌套的 effect:
    function run() {
      if (!effectStack.includes(this)) {
        try {
          effectStack.push((activeEffect = this))
          return this.fn()
        } finally {
          effectStack.pop()
          activeEffect = effectStack[effectStack.length - 1]
        }
      }
    }
    
    • 解决问题:组件嵌套时的依赖关系混乱。

2. Cleanup 机制

  • 每次 effect 执行前清理旧依赖:
    function run() {
      cleanup(this) // 清理之前收集的旧依赖
      // ...然后重新收集新依赖
    }
    
    • 解决问题:动态分支逻辑导致的无效依赖(如 v-if 切换导致的条件依赖)。

3. Lazy 执行

  • 可配置不立即执行 effect:
    const runner = effect(fn, { lazy: true })
    runner() // 手动执行
    
    • 应用场景: computed 属性初始化时延迟计算。

四、与 Vue 各组件的关联

1. 组件渲染

  • 组件 render 函数被包裹在 effect 中:
    function setupRenderEffect(instance) {
      effect(() => {
        const subTree = instance.render.call(instance.proxy)
        patch(instance.subTree, subTree)
        instance.subTree = subTree
      }, { scheduler: queueJob }) // 异步更新队列
    }
    

2. Computed 实现

  • computed 通过 effect + 调度器实现懒更新:
    const computedRef = new ComputedRefImpl(
      getter,
      () => { // 调度器
        if (!this._dirty) {
          this._dirty = true
          trigger(this, 'set', 'value')
        }
      }
    )
    

3. Watch API

  • watch 基于 effect 的调度器实现异步回调:
    function watch(source, cb, { flush } = {}) {
      let scheduler
      if (flush === 'sync') {
        scheduler = cb
      } else { // 'post' 或其他默认情况
        scheduler = () => queuePostFlushCb(cb)
      }
    
      effect(() => traverse(source), { scheduler })
    }
    

五、与 Vue 2 的对比

特性 Vue 2 (Watcher) Vue 3 (Effect)
依赖追踪 通过遍历数据触发 getter 通过 Proxy/Reflect 自动追踪
更新粒度 依赖组件级检查 基于精确依赖的靶向更新
性能优化 需手写 pureComputed 内置自动的依赖清理和调度机制
内存管理 易产生内存泄漏(旧 Dep 引用问题) 通过 WeakMap 自动释放无用依赖

六、源码流程图解

+---------------------+
|   Reactive Object   |
+----------+----------+
           │ 访问属性时
           ▼
+---------------------+
|   触发 get 代理     +----→ track(target, key)
+---------------------+         │
           ▲                    ▼ 存储依赖关系
           │          +---------------------+
           +----------+    targetMap        |
                      | (WeakMap结构)        |
                      +---------+-----------+
                                │
                                ▼
                      +---------------------+
                      |   depsMap (Map)     |
                      | (key → Dep Set)      |
                      +---------+-----------+
                                │
                                ▼
                      +---------------------+
                      |   dep (Set)         |
                      | (存储所有关联的 effect)|
                      +---------------------+

总结

Vue 3 的 effect 通过以下机制成为响应式系统的核心:

  1. Proxy 依赖收集:精确追踪响应式数据的使用。
  2. 调度器控制:提供灵活的回调执行方式。
  3. 内存安全:通过 WeakMap 自动管理依赖。
  4. 框架级优化:支持组件渲染、计算属性、watch 等核心功能。
posted @   木燃不歇  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Blazor Hybrid适配到HarmonyOS系统
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· 解决跨域问题的这6种方案,真香!
· 分享4款.NET开源、免费、实用的商城系统
· 一套基于 Material Design 规范实现的 Blazor 和 Razor 通用组件库
点击右上角即可分享
微信分享提示