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 会被自动清理,不会再触发

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

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

相关推荐
豆苗学前端几秒前
10分钟带你入门websocket,并实现一个在线多人聊天室
前端·javascript·后端
白水清风2 分钟前
Vue3之渲染器
前端·vue.js·面试
刘永胜是我6 分钟前
解决Volta环境下npm全局包卸载失败:一次深入排查之旅
前端·node.js
白水清风7 分钟前
Vue3之组件化
前端·vue.js·面试
luckyPian9 分钟前
ES6+新特性:ES7(二)
开发语言·javascript·ecmascript
边洛洛10 分钟前
解决[PM2][ERROR] Script not found: D:\projects\xxx\start
前端·javascript
白水清风13 分钟前
Vue3之响应式系统
vue.js·面试·前端工程化
农夫山泉的小黑24 分钟前
【DeepSeek帮我准备前端面试100问】(十八)Reflect在vue3的使用
前端·面试
Achieve前端实验室31 分钟前
【每日一面】手写防抖函数
前端·面试·node.js
三十_35 分钟前
TypeORM 多对多关联篇:中间表、JoinTable 与复杂关系的建模
前端·后端