【Vue3源码学习】— CH2.5 reactiveEffect.ts:Vue 3响应式系统的核心
reactiveEffect.ts:Vue 3响应式系统的核心
- 1. 什么是 reactiveEffect?
- 2. 核心机制
- 2.1 依赖收集(Track)
- 2.2 触发更新(Trigger)
- 2.3 效果范围(effectScope)
- 3. 源码解析 —— track
- 3.1 track
- 3.2 参数 target 究竟是什么
- 3.2.1 target是什么
- 3.2.2 怎么理解target
- 3.2.3 小结
- 4. 源码解析 —— trigger
- 5. 应用场景
- 5.1 组件渲染
- 5.2 计算属性
- 5.3 观察者(watch)
- 6. 小结
Vue 3的响应式系统是基于Proxy和Reflect API构建的,其中reactiveEffect扮演了核心角色,实现了数据的响应式变化追踪和视图的自动更新。本章节我们将深入探讨reactiveEffect的工作原理。
1. 什么是 reactiveEffect?
在Vue中,每当我们操作响应式数据时,都会触发reactiveEffect来跟踪这些操作。无论是计算属性、watch监听器,还是组件的渲染函数,都被视为副作用函数,并被reactiveEffect所管理。
2. 核心机制
2.1 依赖收集(Track)
track函数负责在副作用函数首次执行时收集所有被访问的响应式数据的依赖。这意味着,当一个数据项被读取时,所有依赖于这个数据的副作用函数都会被记录下来。
2.2 触发更新(Trigger)
当响应式数据变化时,trigger函数会找到所有依赖于这个数据的副作用函数,并重新执行它们,从而实现数据到视图的自动更新。
2.3 效果范围(effectScope)
effectScope是Vue 3引入的新概念,它允许开发者组织和管理多个reactiveEffect。这对于在组件卸载或需要清理副作用时非常有用,避免内存泄漏。
3. 源码解析 —— track
3.1 track
/*** track函数负责追踪一个响应式对象的属性访问操作** 它的主要作用是确定当前哪个副作用函数(effect)正在运行* 并将这个副作用函数记录为该响应式属性的依赖** @param target - 被访问属性所属的响应式对象.* @param type - 访问类型,由TrackOpTypes枚举定义,比如读取、写入.* @param key - 被访问的响应式属性的标识符.*/
export function track(target: object, type: TrackOpTypes, key: unknown) {/*** 1.条件判断: 首先检查是否应该进行依赖收集,这由shouldTrack和activeEffect共同决定。* shouldTrack是一个标志,表明是否应该收集依赖;* activeEffect指的是当前正在执行的副作用函数。*/if (shouldTrack && activeEffect) {/*** 2.获取依赖映射: 使用targetMap(一个全局WeakMap,在上一章节详细介绍过)尝试获取当前对象的依赖映射depsMap。* 如果不存在,就为这个对象创建一个新的Map并设置到targetMap中。*/let depsMap = targetMap.get(target)if (!depsMap) {targetMap.set(target, (depsMap = new Map()))}/*** 3. 获取依赖集合: 尝试从depsMap中获取对应属性key的依赖集合dep。* 如果这个属性还没有依赖集合,就创建一个新的依赖集合,并设置一个清理函数,当依赖集合为空时从depsMap中移除这个属性的记录*/let dep = depsMap.get(key)if (!dep) {depsMap.set(key, (dep = createDep(() => depsMap!.delete(key))))}/*** 4. 追踪效果: 最后,使用trackEffect函数将当前的activeEffect(副作用函数)添加到这个属性的依赖集合中。* 如果是开发模式(__DEV__为true),还会传递额外的调试信息,包括被访问的对象、访问类型和属性标识符。*/trackEffect(activeEffect,dep,__DEV__? {target,type,key,}: void 0,)}
}
3.2 参数 target 究竟是什么
在 baseHandlers.ts 里的get函数里,我们说target是原始对象,而在track函数里,我们说target是被访问属性所属的响应式对象,但是它们又是同一个值,这让人很困惑。
3.2.1 target是什么
看一下proxy里关于捕获器的实例:
const target ={foo: 'bar'
}
const handler={get(trapTarget, property, receiver) {console.log(trapTarget === target);console.log(property);console.log(receiver === proxy); }
}
const proxy = new Proxy(target, handler);
proxy.foo;//输出
//true
//foo
//true
所以在技术上无论是get函数里,还是track函数里,target是原始对象。
3.2.2 怎么理解target
看一下proxy最基础的示例:
const target={id:'target'
};
const handle={};
const proxy = new Proxy(target,handle);// id 属性会访问同一个值
console.log(target.id); // target
console.log(proxy.id); // target// 给目标(target)属性赋值会反映在两个对象上 ,因为两个对象访问的是同一个值
target.id = 'foo';
console.log(target.id); // foo
console.log(proxy.id); // foo
3.2.3 小结
简而言之,尽管track的参数是原始对象,但在Vue的响应式系统上下文中,我们可以将其等同于它的响应式代理,因为所有操作实际上都是针对这个代理执行的。
4. 源码解析 —— trigger
/*** trigger函数负责找到所有依赖于这些数据的副作用函数,* 并执行它们以更新视图或执行其他副作用** @param target - 发生变化的响应式对象.* @param type - 变化的类型,由TriggerOpTypes枚举定义,如设置(SET)、添加(ADD)、删除(DELETE)等.* @param key - 发生变化的具体属性名.*/
export function trigger(target: object,type: TriggerOpTypes,key?: unknown,newValue?: unknown, //新值(对于SET操作)oldValue?: unknown, //旧值(对于SET操作)oldTarget?: Map<unknown, unknown> | Set<unknown>, //旧的集合对象(对于集合类型,如Map、Set的变化)
) {/*** 寻找依赖:如果depsMap不存在,意味着target从未被追踪过依赖,直接返回*/const depsMap = targetMap.get(target)if (!depsMap) {// never been trackedreturn}/*** 确定需要触发的依赖集合(deps):根据变化的类型(type)和具体的属性(key),确定需要触发的依赖集合* 特殊情况处理:如整个集合被清除(CLEAR)、数组长度变化、Map集合的特定操作等,都有针对性的逻辑来确定哪些依赖需要被触发*/let deps: (Dep | undefined)[] = []if (type === TriggerOpTypes.CLEAR) {// collection being cleared// trigger all effects for targetdeps = [...depsMap.values()]} else if (key === 'length' && isArray(target)) {const newLength = Number(newValue)depsMap.forEach((dep, key) => {if (key === 'length' || (!isSymbol(key) && key >= newLength)) {deps.push(dep)}})} else {// schedule runs for SET | ADD | DELETEif (key !== void 0) {deps.push(depsMap.get(key))}// also run for iteration key on ADD | DELETE | Map.SETswitch (type) {case TriggerOpTypes.ADD:if (!isArray(target)) {deps.push(depsMap.get(ITERATE_KEY))if (isMap(target)) {deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))}} else if (isIntegerKey(key)) {// new index added to array -> length changesdeps.push(depsMap.get('length'))}breakcase TriggerOpTypes.DELETE:if (!isArray(target)) {deps.push(depsMap.get(ITERATE_KEY))if (isMap(target)) {deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))}}breakcase TriggerOpTypes.SET:if (isMap(target)) {deps.push(depsMap.get(ITERATE_KEY))}break}}/*** 触发依赖更新:* 遍历所有需要触发的依赖集合deps,对于每个依赖(即副作用函数集合),调用triggerEffects函数来实际执行它们。* 在执行依赖前,调用pauseScheduling暂停调度(防止在依赖执行过程中产生无限循环调用),执行完后调用resetScheduling恢复调度。*/pauseScheduling()for (const dep of deps) {if (dep) {triggerEffects(dep,DirtyLevels.Dirty,__DEV__? {target,type,key,newValue,oldValue,oldTarget,}: void 0,)}}resetScheduling()
}
5. 应用场景
5.1 组件渲染
Vue 组件的渲染过程本身是一个副作用。Vue 使用reactiveEffect来自动重新渲染组件,当组件依赖的响应式数据变化时。
5.2 计算属性
计算属性是基于其它响应式数据计算得出的值。Vue内部使用reactiveEffect来跟踪计算属性依赖的数据,确保它们在依赖数据变化时更新。
5.3 观察者(watch)
Vue的watchAPI允许你观察响应式数据的变化,并在变化时执行回调函数。这背后也是使用reactiveEffect实现的。
6. 小结
通过本章的学习,我们了解了reactiveEffect在Vue 3响应式系统中的核心作用:依赖收集与更新触发。
而这里涉及的一些关键逻辑,如:shouldTrack、activeEffect等,我们会在下一个章节继续探讨。

相关文章:
【Vue3源码学习】— CH2.5 reactiveEffect.ts:Vue 3响应式系统的核心
reactiveEffect.ts:Vue 3响应式系统的核心 1. 什么是 reactiveEffect?2. 核心机制2.1 依赖收集(Track)2.2 触发更新(Trigger)2.3 效果范围(effectScope) 3. 源码解析 —— track3.1 …...
K8S的mountPath和subPath
1 mountPath mountPath是容器内部文件系统的挂载点,它定义了容器内部将外部存储卷(如 PersistentVolume、ConfigMap、Secret 等)挂载到哪个路径下。通过 mountPath,容器可以访问这些挂载的数据或配置。 2 subPath subPath 是 m…...
notepad++里安装32位和64位的16进制编辑器Hex-Editor
这个16进制编辑器确实是个好东西,平时工作种会经常用到, 这是hex-editor的官网。这个里边只能下载32位的(64位的看最下边),选一个合适的版本,我当时选的是最新的版本 https://sourceforge.net/projects/npp-plugins/files/Hex%20E…...
Python类的基本结构
当我们在Python中定义类时,我们实际上是在创建一种新的数据类型。类允许我们定义对象的属性和方法,从而构建更复杂的程序。让我们深入探讨一下关于类的一些重要概念。 定义类:基本结构 一个类的基本结构包括以下部分: 类名&…...
利用HIVE的窗口函数进行SQL查询中出现的问题记录
student_info部分数据 score_info部分数据 course_info 1、问题复现 --完整SQL selectsti.stu_id,sti.stu_name,concat_ws(",",collect_set(ci.course_name)) over(partition by sti.stu_id) fromstudent_info sti left joinscore_info sci onsti.stu_idsci.stu_id l…...
更改chatglm认知
ChatGLM-Efficient-Tuning 下载源代码 下载ChatGLM-Efficient-Tuning 解压 创建虚拟环境 conda create --prefixD:\CondaEnvs\chatglm6btrain python3.10 cd D:\ChatGLM-Efficient-Tuning-main conda activate D:\CondaEnvs\chatglm6btrain安装所需要的包 pip install -r…...
WPF 界面命令绑定(MVVM结构)
1.创建模型数据类(M) /// <summary>/// 数据模型/// </summary>public class LoginDataModel{// 用户名private string _userName;public string UserName{get { return _userName; }set{_userName value;}}// 密码private string _passWor…...
常见手撕项目C++
常见手撕项目C 设计模式单例模式饿汉模式懒汉模式 策略模式策略接口实现具体的策略(虚函数重写)定义上下文用户调用 设计模式 单例模式 单例模式是一种常用的软件设计模式,其目的是确保一个类只有一个实例,并提供一个全局访问点来…...
创建一个批处理作业来处理大量数据,例如从数据库中读取数据并进行处理
创建一个批处理作业来处理大量数据,例如从数据库中读取数据并进行处理 要创建一个批处理作业来处理大量数据,您可以使用Spring Batch。Spring Batch是一个用于大规模批处理的框架,它提供了丰富的功能来处理复杂的批处理任务,如读…...
LeetCode 2.两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。 请你将两个数相加,并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外,这两个数都不会以 0 …...
如何利用ChatGPT提升学术论文写作效率
ChatGPT无限次数:点击直达 如何利用ChatGPT提升学术论文写作效率 ChatGPT 是一种基于大规模预训练模型的自然语言处理工具,可以在各种文本生成任务中发挥作用,包括学术论文写作。利用ChatGPT,可以提高学术论文写作的速度和质量,帮…...
LLMs之Mistral:Mistral 7B v0.2的简介、安装和使用方法、案例应用之详细攻略
LLMs之Mistral:Mistral 7B v0.2的简介、安装和使用方法、案例应用之详细攻略 导读:Mistral AI首个7B模型发布于2023年9月,在基准测试中超越Llama 2 13B,一下子声名大振。Mistral 7B v0.2对应的指令调优版本Mistral-7B-Instruct-v0…...
深入解析Oracle数据库中的WITH AS(CTE)原理
Oracle数据库中的WITH AS子句(也称为公用表表达式CTE(Common Table Expression))是一种高级查询构造工具,它允许在一条SQL语句的开始部分定义临时的结果集(或称子查询),这个结果集可以被随后的查询主体多次…...
Linux 环境安装 Elasticsearch 8.X
安装前说明 首先确定操作系统,在Linux发行版上执行uname -a查看具体系统。我是Ubuntu系统,可以用直接用apt-get安装,也可以下载tar.gz包手动安装。使用apt-get安装更方便快速,但不同的文件会被安装到不同的目录,不方便…...
Java零基础-集合:函数式接口
哈喽,各位小伙伴们,你们好呀,我是喵手。 今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。 我是一名后…...
Redis Scan指令解析与使用示例
Redis Scan指令解析与使用示例 概念 想要从redis key列表中找到某个key,redis提供了一个简单粗暴的指令keys用来列出满足查询条件的所有key。 keys redis* keys redis*keykey指令非常简单,只要提供一个简单的正则表达式即可,但是有两个明显的…...
Qt+OpenGL入门教程(三)——绘制三角形
通过前两篇文章的学习,我想大家应该有了基本的理解,我们接下来实操一下。 创建Qt OpenGL窗口 QOpenGLWidget QGLWidget是传统QtOpenGL模块的一部分,与其他QGL类一样,应该在新的应用程序中避免使用。相反,从Qt5.4开始…...
springcloud基本使用(搭建eureka服务端)
创建springbootmaven项目 next next finish创建成功 删除项目下所有文件目录,只保留pox.xml文件 父项目中的依赖: springboot依赖: <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-s…...
第十二章:预处理命令
文章目录 第十二章:预处理命令宏定义无参宏定义带参数的宏定义 文件包含处理 第十二章:预处理命令 作用:由编译预处理程序对程序中的特殊命令作出解释,以产生新的源程序对其进行正式编译 C语言与其他语言的重要区别就是可以使用预…...
Game Audio Programming
音频编程时游戏开发中最容易忽略,学习资源又是很少的环节。接下来,你将和我探索人耳的工作机制。 what is sound? 我们可以解释电视机是如何通过眼睛传递视觉信息的,但却往往无法对听觉信息做出类似的解释。 对声音的科学研究被称为声学&…...
如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...
Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...
Monorepo架构: Nx Cloud 扩展能力与缓存加速
借助 Nx Cloud 实现项目协同与加速构建 1 ) 缓存工作原理分析 在了解了本地缓存和远程缓存之后,我们来探究缓存是如何工作的。以计算文件的哈希串为例,若后续运行任务时文件哈希串未变,系统会直接使用对应的输出和制品文件。 2 …...
Easy Excel
Easy Excel 一、依赖引入二、基本使用1. 定义实体类(导入/导出共用)2. 写 Excel3. 读 Excel 三、常用注解说明(完整列表)四、进阶:自定义转换器(Converter) 其它自定义转换器没生效 Easy Excel在…...
Python爬虫(52)Scrapy-Redis分布式爬虫架构实战:IP代理池深度集成与跨地域数据采集
目录 一、引言:当爬虫遭遇"地域封锁"二、背景解析:分布式爬虫的两大技术挑战1. 传统Scrapy架构的局限性2. 地域限制的三种典型表现 三、架构设计:Scrapy-Redis 代理池的协同机制1. 分布式架构拓扑图2. 核心组件协同流程 四、技术实…...
