Vue3 EffectScope 源码解析与理解

作者:excel日期:2025/10/7

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

  • activeEffectScope:表示当前正在运行的 effect 作用域。
  • EffectScope 类:用来管理一组副作用(ReactiveEffect),提供生命周期控制。
1import type { ReactiveEffect } from './effect'
2import { warn } from './warning'
3
4// 当前全局正在运行的作用域
5export let activeEffectScope: EffectScope | undefined
6
7export class EffectScope {
8  private _active = true              // 是否活跃
9  private _on = 0                     // on() 调用计数
10  effects: ReactiveEffect[] = []      // 收集的副作用
11  cleanups: (() => void)[] = []       // 清理回调
12  private _isPaused = false           // 是否暂停
13
14  parent: EffectScope | undefined     // 父作用域
15  scopes: EffectScope[] | undefined   // 子作用域
16  private index: number | undefined   // 在父作用域数组中的索引
17

2. 构造函数与层级关系

  • 构造时会判断是否 detached(独立作用域)。
  • detached 时,会自动挂到当前 activeEffectScopescopes 中,形成父子层级。
1  constructor(public detached = false) {
2    this.parent = activeEffectScope
3    if (!detached && activeEffectScope) {
4      this.index =
5        (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(this) - 1
6    }
7  }
8
9  get active(): boolean {
10    return this._active
11  }
12

3. 暂停与恢复

  • pause():暂停本作用域及子作用域的所有副作用。
  • resume():恢复运行。
1  pause(): void {
2    if (this._active) {
3      this._isPaused = true
4      if (this.scopes) {
5        for (let i = 0; i < this.scopes.length; i++) {
6          this.scopes[i].pause()
7        }
8      }
9      for (let i = 0; i < this.effects.length; i++) {
10        this.effects[i].pause()
11      }
12    }
13  }
14
15  resume(): void {
16    if (this._active && this._isPaused) {
17      this._isPaused = false
18      if (this.scopes) {
19        for (let i = 0; i < this.scopes.length; i++) {
20          this.scopes[i].resume()
21        }
22      }
23      for (let i = 0; i < this.effects.length; i++) {
24        this.effects[i].resume()
25      }
26    }
27  }
28

4. 执行函数上下文

  • run(fn):在当前作用域环境下执行函数。
  • 内部会切换 activeEffectScope,保证新副作用挂到正确的 scope。
1  run<T>(fn: () => T): T | undefined {
2    if (this._active) {
3      const currentEffectScope = activeEffectScope
4      try {
5        activeEffectScope = this
6        return fn()
7      } finally {
8        activeEffectScope = currentEffectScope
9      }
10    } else if (__DEV__) {
11      warn(`cannot run an inactive effect scope.`)
12    }
13  }
14

5. 手动 on/off 控制

  • on():进入 scope,记录之前的 scope。
  • off():退出 scope,还原到上一个 scope。
1  prevScope: EffectScope | undefined
2
3  on(): void {
4    if (++this._on === 1) {
5      this.prevScope = activeEffectScope
6      activeEffectScope = this
7    }
8  }
9
10  off(): void {
11    if (this._on > 0 && --this._on === 0) {
12      activeEffectScope = this.prevScope
13      this.prevScope = undefined
14    }
15  }
16

6. 停止(销毁)

  • stop():彻底销毁本 scope。
    • 停止所有副作用。
    • 调用清理回调。
    • 停止所有子 scope。
    • 从父作用域中移除自己,避免内存泄漏。
1  stop(fromParent?: boolean): void {
2    if (this._active) {
3      this._active = false
4      for (let i = 0; i < this.effects.length; i++) {
5        this.effects[i].stop()
6      }
7      this.effects.length = 0
8
9      for (let i = 0; i < this.cleanups.length; i++) {
10        this.cleanups[i]()
11      }
12      this.cleanups.length = 0
13
14      if (this.scopes) {
15        for (let i = 0; i < this.scopes.length; i++) {
16          this.scopes[i].stop(true)
17        }
18        this.scopes.length = 0
19      }
20
21      if (!this.detached && this.parent && !fromParent) {
22        const last = this.parent.scopes!.pop()
23        if (last && last !== this) {
24          this.parent.scopes![this.index!] = last
25          last.index = this.index!
26        }
27      }
28      this.parent = undefined
29    }
30  }
31}
32

7. 工具函数

  • effectScope(detached):创建新的作用域。
  • getCurrentScope():获取当前活跃作用域。
  • onScopeDispose(fn):在当前作用域注册清理函数。
1export function effectScope(detached?: boolean): EffectScope {
2  return new EffectScope(detached)
3}
4
5export function getCurrentScope(): EffectScope | undefined {
6  return activeEffectScope
7}
8
9export function onScopeDispose(fn: () => void, failSilently = false): void {
10  if (activeEffectScope) {
11    activeEffectScope.cleanups.push(fn)
12  } else if (__DEV__ && !failSilently) {
13    warn(
14      `onScopeDispose() is called when there is no active effect scope` +
15        ` to be associated with.`,
16    )
17  }
18}
19

使用示例:简化版 effectScope

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

1import { ref, watch, effectScope } from 'vue'
2
3const scope = effectScope()
4
5scope.run(() => {
6  const count = ref(0)
7
8  // 这个 watch 会被 scope 收集
9  watch(count, (newVal) => {
10    console.log('count changed:', newVal)
11  })
12
13  // 模拟修改
14  count.value++
15  count.value++
16})
17
18// 当我们不需要这个 scope 里的副作用时
19scope.stop()
20// 此时 watch 会被自动清理,不会再触发
21

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

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


Vue3 EffectScope 源码解析与理解》 是转载文章,点击查看原文


相关推荐


Linus 眼中,编程 AI 的真实价值如何?
飞哥数智谈2025/10/6

今天刷到了一段视频,是 Linux 之父 Linus Torvalds 与 VMware 副总裁兼首席开源官 Dirk Hohndel 的一段对话。 内容挺有意思,分享给大家。 主要有两个话题: AI 只是打了鸡血的自动纠错 AI 幻觉带来了 bug 话题是由 Dirk Hohndel 提出,由 Linus Torvalds 进行回答的。 AI 是打了鸡血的自动纠错 针对这一点,Linus Torvalds 认为这一说法有一定的合理性,但 AI 在编程领域的真正潜力是可以成为一个识别“明显愚


从传输层协议到 UDP:轻量高效的传输选择
渡我白衣2025/10/5

前言 在计算机网络中,传输层是一个关键的层级,它为应用进程之间的通信提供了端到端的传输服务。常见的传输层协议有 TCP 和 UDP。前者强调可靠、面向连接的传输,而后者则提供轻量级、无连接的通信方式。传输层位于网络层之上、应用层之下,为进程之间提供端到端的数据传输服务。要理解 UDP 协议,我们需要先了解 传输层的功能与常见协议,再深入探讨为什么 UDP 在今天的网络环境中仍占据着举足轻重的地位。 一、传输层的基本概念 在 OSI 七层模型 和 TCP/IP 五层模型 中,传输层是第四层,它的主


磁盘的理解&&CHS和LBA地址转换
阑梦清川2025/10/3

1.对于磁盘的理解 首先就是我们的操作系统课本上面学习的这个磁盘的基本结构,比如下面的这个磁盘,磁头,磁头臂以及柱面,扇区的相关的概念; 针对于这个部分的内容,我自己也没有什么经验可以分享,因为这个东西就是固定的,唯一需要注意的就是结合图区进行理解,注意分别代表的是我们的图片里面画出来的这个磁盘的那一个具体的部分; 根据上面的内容,我们想要确定扇区只需要 CHS 三个部分即可,C 代表的就是我们的柱面,H 表示的是磁头,S 表示的是扇区,下面的这个是 ima 给出来的具体介绍,不懂就多去问问


2181、合并零之间的节点
Lenyiin2025/10/2

2181、[中等] 合并零之间的节点 1、问题描述: 给你一个链表的头节点 head ,该链表包含由 0 分隔开的一连串整数。链表的 开端 和 末尾 的节点都满足 Node.val == 0 。 对于每两个相邻的 0 ,请你将它们之间的所有节点合并成一个节点,其值是所有已合并节点的值之和。然后将所有 0 移除,修改后的链表不应该含有任何 0 。 返回修改后链表的头节点 head 。 2、代码思路: 跳过第一个节点:链表的开头和结尾都包含值为 0 的节点,我们从第二个节点开始处理(即


【c++】深入理解string类(3):典型OJ题
zzzsde2025/10/2

一 仅仅反转字母 链接如下:https://leetcode-cn.com/problems/reverse-only-letters/submissions/ 思路: 这道题目的核心就是交换,我们发现这个逻辑和当时在数据结构里学的快速排序非常类似:两个指针,一个指向开头,一个指向结尾,如果前一个指针的值小于后面指针的值,就交换。相应的在这道题目:如果前一个指针和后一个指针都是字母,那就交换。 所以我们需要先写一个判断是否是字母的函数。(c语言库中有这个函数,如果记得这个函数的名称和


ASCII 码表
IMPYLH2025/10/2

ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是一种字符编码标准,用于表示一组特定的 95 个(英语)可打印字符和 33 个控制字符(共 128 个代码点)。 二进制八进制十进制十六进制字符010 00000403220space (no visible glyph)010 00010413321!010 00100423422"010 00110433523#010 01000443624$010 01


软件设计师软考备战:第五篇 软件工程与项目管理
软考和人工智能学堂10/1/2025

​​软件工程​​是应用系统化、规范化、可量化的方法开发、运行和维护软件的学科。其目标是提高软件质量、降低开发成本、保证开发进度。​​软件危机​​的表现:项目超出预算项目超过计划完成时间软件质量低下软件通常不满足需求项目无法管理,代码难以维护软件工程与项目管理是软件设计师必须掌握的核心知识,不仅关系到软件开发的成功,也直接影响软件质量和项目效益。通过系统学习软件开发全过程和项目管理方法,能够提高软件开发的专业水平和管理能力。​​思考题​。


[wps_clear]wps清理残余 ——注册表不干净
拾贰_Python9/30/2025

WPS卸载后常见的注册表残留位置包括:HKEY_CURRENT_USER和HKEY_LOCAL_MACHINE下的Kingsoft目录、HKEY_CLASSES_ROOT中与WPS相关的条目。此外,HKEY_CLASSES_ROOT\Applications下可能存在wpp.exe相关项,以及HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FileExts.wps等文件关联记录的残留。这些注册表项需要手动清理以实现完全


tcp服务器
liuy96152025/10/9

🧩 一、总体架构思路 TCP 服务器的基本流程: 创建监听套接字 → 绑定 IP 和端口 → 监听端口 → 接受连接 → 通信收发 → 关闭连接 伪代码框架: int main() { int listen_fd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP套接字 bind(listen_fd, …); // 绑定地址和端口 listen(listen_fd, SOMAXCONN);


对《DDD本质论》一文的解读
canonical_entropy2025/10/10

在《DDD本质论:从哲学到数学,再到工程实践的完整指南之理论篇》中,我们建立了一套从第一性原理出发的DDD理论体系。由于原文理论密度较高、概念间关系精微,为帮助读者更清晰地把握其思想脉络,我们设计了一项思想实验,并借助AI进行体系梳理与对比。 我们首先向AI提出以下问题: DDD领域驱动设计的概念有哪些?这些概念之间的相互关系是什么。如果要逐一去掉这些概念,你会按照什么顺序,为什么? 换言之,如果从第一性原理出发,如何逐步推导出这些概念的相互关系? 不要把任何技术看作是一个不可切分的整体,任何

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0