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 时,会自动挂到当前 activeEffectScope 的 scopes 中,形成父子层级。
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 会被自动清理,不会再触发

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

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

相关推荐
ss2732 分钟前
AI全栈实战 | 3.3-02 Python 并发:有 GIL 为什么还用多线程,asyncio 和 JS 事件循环同源不同味
开发语言·javascript·python
liangshanbo121518 分钟前
高级前端面试题:package.json 常见字段
前端·json
CappuccinoRose38 分钟前
输入事件进阶
前端·javascript·输入事件
ShineWinsu1 小时前
对于Redis:AOF持久化的解析
linux·数据库·redis·缓存·面试·持久化·aof
liangshanbo12152 小时前
面试题:前端工程化中 Tree Shaking的原理是什么?
前端·treeshaking
500843 小时前
React Native for OpenHarmony 实战:三方库 react-native-url-polyfill 的鸿蒙化适配指南
javascript·react native·react.js·性能优化·electron·harmonyos
补码缩写3 小时前
视频跳转触发了seeked,画面就到位了吗
前端
考研保研资料分享3 小时前
金融专硕保研经验:清华五道口预推免、北大汇丰夏令营与面试复盘
面试·金融
事圆则缓3 小时前
Kotlin 入门与面试:从空安全、扩展函数到协程
安全·面试·kotlin
库拉镜像AI牛牛3 小时前
漫剧工作室量产方案:依托知漫剧 AI 短剧降本增效
大数据·服务器·前端·人工智能·语音识别