Vue3 EffectScope 源码解析与理解

1. 全局变量与基础类定义

  • activeEffectScope:表示当前正在运行的 effect 作用域。
  • EffectScope 类:用来管理一组副作用(ReactiveEffect),提供生命周期控制。
typescript 复制代码
import type { ReactiveEffect } from './effect'
import { warn } from './warning'

// 当前全局正在运行的作用域
export let activeEffectScope: EffectScope | undefined

export class EffectScope {
  private _active = true              // 是否活跃
  private _on = 0                     // on() 调用计数
  effects: ReactiveEffect[] = []      // 收集的副作用
  cleanups: (() => void)[] = []       // 清理回调
  private _isPaused = false           // 是否暂停

  parent: EffectScope | undefined     // 父作用域
  scopes: EffectScope[] | undefined   // 子作用域
  private index: number | undefined   // 在父作用域数组中的索引

2. 构造函数与层级关系

  • 构造时会判断是否 detached(独立作用域)。
  • detached 时,会自动挂到当前 activeEffectScopescopes 中,形成父子层级。
kotlin 复制代码
  constructor(public detached = false) {
    this.parent = activeEffectScope
    if (!detached && activeEffectScope) {
      this.index =
        (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(this) - 1
    }
  }

  get active(): boolean {
    return this._active
  }

3. 暂停与恢复

  • pause():暂停本作用域及子作用域的所有副作用。
  • resume():恢复运行。
kotlin 复制代码
  pause(): void {
    if (this._active) {
      this._isPaused = true
      if (this.scopes) {
        for (let i = 0; i < this.scopes.length; i++) {
          this.scopes[i].pause()
        }
      }
      for (let i = 0; i < this.effects.length; i++) {
        this.effects[i].pause()
      }
    }
  }

  resume(): void {
    if (this._active && this._isPaused) {
      this._isPaused = false
      if (this.scopes) {
        for (let i = 0; i < this.scopes.length; i++) {
          this.scopes[i].resume()
        }
      }
      for (let i = 0; i < this.effects.length; i++) {
        this.effects[i].resume()
      }
    }
  }

4. 执行函数上下文

  • run(fn):在当前作用域环境下执行函数。
  • 内部会切换 activeEffectScope,保证新副作用挂到正确的 scope。
javascript 复制代码
  run<T>(fn: () => T): T | undefined {
    if (this._active) {
      const currentEffectScope = activeEffectScope
      try {
        activeEffectScope = this
        return fn()
      } finally {
        activeEffectScope = currentEffectScope
      }
    } else if (__DEV__) {
      warn(`cannot run an inactive effect scope.`)
    }
  }

5. 手动 on/off 控制

  • on():进入 scope,记录之前的 scope。
  • off():退出 scope,还原到上一个 scope。
kotlin 复制代码
  prevScope: EffectScope | undefined

  on(): void {
    if (++this._on === 1) {
      this.prevScope = activeEffectScope
      activeEffectScope = this
    }
  }

  off(): void {
    if (this._on > 0 && --this._on === 0) {
      activeEffectScope = this.prevScope
      this.prevScope = undefined
    }
  }

6. 停止(销毁)

  • stop():彻底销毁本 scope。

    • 停止所有副作用。
    • 调用清理回调。
    • 停止所有子 scope。
    • 从父作用域中移除自己,避免内存泄漏。
kotlin 复制代码
  stop(fromParent?: boolean): void {
    if (this._active) {
      this._active = false
      for (let i = 0; i < this.effects.length; i++) {
        this.effects[i].stop()
      }
      this.effects.length = 0

      for (let i = 0; i < this.cleanups.length; i++) {
        this.cleanups[i]()
      }
      this.cleanups.length = 0

      if (this.scopes) {
        for (let i = 0; i < this.scopes.length; i++) {
          this.scopes[i].stop(true)
        }
        this.scopes.length = 0
      }

      if (!this.detached && this.parent && !fromParent) {
        const last = this.parent.scopes!.pop()
        if (last && last !== this) {
          this.parent.scopes![this.index!] = last
          last.index = this.index!
        }
      }
      this.parent = undefined
    }
  }
}

7. 工具函数

  • effectScope(detached):创建新的作用域。
  • getCurrentScope():获取当前活跃作用域。
  • onScopeDispose(fn):在当前作用域注册清理函数。
javascript 复制代码
export function effectScope(detached?: boolean): EffectScope {
  return new EffectScope(detached)
}

export function getCurrentScope(): EffectScope | undefined {
  return activeEffectScope
}

export function onScopeDispose(fn: () => void, failSilently = false): void {
  if (activeEffectScope) {
    activeEffectScope.cleanups.push(fn)
  } else if (__DEV__ && !failSilently) {
    warn(
      `onScopeDispose() is called when there is no active effect scope` +
        ` to be associated with.`,
    )
  }
}

使用示例:简化版 effectScope

在 Vue3 中,setup() 里创建的副作用(watch/computed)会自动挂到组件的 scope 上。这里我们用手动的 effectScope 来演示:

javascript 复制代码
import { ref, watch, effectScope } from 'vue'

const scope = effectScope()

scope.run(() => {
  const count = ref(0)

  // 这个 watch 会被 scope 收集
  watch(count, (newVal) => {
    console.log('count changed:', newVal)
  })

  // 模拟修改
  count.value++
  count.value++
})

// 当我们不需要这个 scope 里的副作用时
scope.stop()
// 此时 watch 会被自动清理,不会再触发

✅ 这样一篇文章逻辑清晰,最后有示例,能从源码理解到实际应用。

本文内容由人工智能生成,仅供学习与参考使用,请在实际应用中结合自身情况进行判断。

相关推荐
细节控菜鸡2 小时前
【2025最新】ArcGIS for JS 实现地图卷帘效果
开发语言·javascript·arcgis
UrbanJazzerati3 小时前
考研数学:求根公式
面试
UrbanJazzerati3 小时前
考研数学:巧用柯西不等式
面试
不老刘3 小时前
Base UI:一款极简主义的「无样式」组件库
前端·ui
祈祷苍天赐我java之术3 小时前
Redis 有序集合解析
java·前端·windows·redis·缓存·bootstrap·html
hanxiaozhang20184 小时前
Netty面试重点-1
网络·网络协议·面试·netty
刀客1234 小时前
C++ 面试总结
开发语言·c++·面试
ObjectX前端实验室4 小时前
【react18原理探究实践】React Effect List 构建与 Commit 阶段详解
前端·react.js
序属秋秋秋4 小时前
《C++进阶之C++11》【智能指针】(下)
c++·笔记·学习·面试·c++11·智能指针·新特性