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

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

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

相关推荐
再吃一根胡萝卜5 小时前
08 · 前端:Vue3 + SSE 流式与执行链路可视化
面试
再吃一根胡萝卜5 小时前
03 · 后端:FastAPI 与分层架构
面试
mldong6 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排7 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60617 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角8 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!8 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰9 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
Interview Aid1129 小时前
Walmart Global Tech SDE 三轮面经|基础、并发、压力面
面试·职场和发展
mayaairi10 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js