当前位置: 首页 > news >正文

Vue3源码reactivity响应式篇之ReactiveEffect类

概览

在vue3的响应式系统设计中,ReactiveEffect类是副作用管理的核心类,负责封装所有需要响应式触发的函数(如组件渲染函数,watch回调、计算属性的求值函数等),并实现副作用的依赖收集、触发执行、暂停/恢复、停止等完整生命周期管理。

源码分析

ReactiveEffect的本质就是对副作用函数的封装,它解决如下三个核心问题:

  1. 依赖收集:执行副作用函数时,自动记录它所依赖的响应式数据
  2. 触发更新:当依赖的数据变化时,自动重新执行副作用函数(或通过调度器控制执行时机)
  3. 生命周期管理:支持暂停、恢复、停止副作用,以及清理依赖关系。

ReactiveEffect的源码实现如下:

class ReactiveEffect {constructor(fn) {this.fn = fn; // 副作用函数本体// 依赖管理相关:存储当前副作用依赖的dep(即双向链表结构)this.deps = []; // 依赖链表的头部this.depsTail = null; // 依赖链表的尾部this.flags = 1 | 4; // 标志位, 1:激活 4:需要追踪依赖this.next = null; // 批量更新相关:链表指针用于批量队列中串联多个副作用// 回调与调度器this.cleanup = null; //清理函数(执行前触发,用于清除旧副作用)this.scheduler = null; // 调度器(自定义副作用的执行时机,如watch的flush配置)this.onStop = null;// 停止时的回调this.onTrack = null;// 依赖收集时的调试回调this.onTrigger = null;// 触发更新时的调试回调// 关联到当前活跃的effectScope副作用域中if (activeEffectScope && activeEffect.active) {activeEffectScope.effects.push(this);}}// 暂停副作用pause() {this.flags |= 64; // 标记为暂停状态}// 恢复副作用resume() {if (this.flags & 64) { //若当前副作用已暂停this.flags &= -65; // 清除暂停标记// 若暂停期间有触发更新,则从暂停队列中先清除,再触发更新if (pausedQueueEffects.has(this)) {pausedQueueEffects.delete(this);this.trigger();}}}// 批量更新通知notify() {// 若副作用正在执行,其不允许递归,则不处理if (this.flags & 2 && !(this.flags & 32)) {return;}// 若副作用未调度,则调用`batch`方法if (!(this.flags & 8)) {batch(this);}}// 执行副作用run() {// 若副作用未激活,则直接执行副作用函数,但不追踪依赖。if (!(this.flags & 1)) {return this.fn()}// 标记状态为正在执行this.flags |= 2;// 清理旧依赖cleanupEffect(this);// 准备新的依赖链接prepareDeps(this);// 保存当前活跃的副作用和追踪状态,并切换到当前副作用const prevEffect = activeSub;const prevShouldTrack = shouldTrack;activeSub = this; // 当前副作用成为活跃订阅者,在Dep.track中进行依赖收集shouldTrack = true; //允许依赖收集try {return this.fn(); // 执行副作用函数,(如组件渲染,计算属性求值)} finally {// 清理临时依赖状态cleanupDeps(this);//恢复活跃订阅者以及追踪状态activeSub = prevEffect; // 恢复上一个活跃订阅者shouldTrack = prevShouldTrack; // 恢复上一个追踪状态this.flags &= -3; // 清除正在执行和已暂停标记}}// 停止副作用stop() {if (this.flags & 1) { // 若当前是激活状态// 遍历所有依赖链接,从dep中移除当前副作用for (let link = this.deps; link; link = link.nextDep) {removeSub(link); // 调用removeSub从Dep的订阅者链表中删除}// 清空自身的依赖链表this.deps = this.depsTail = void 0;cleanupEffect(this);//清理残留的以来this.onStop && this.onStop();//触发停止回调this.flags &= -2; // 清除激活状态}}// 触发执行trigger() {if (this.flags & 64) {//若已暂停,则加入暂停队列pausedQueueEffects.add(this);} else if (this.scheduler) {// 若有自定义调度器,则使用调度器执行this.scheduler();} else {//否则调用this.runIfDirty()方法检查是否需要执行this.runIfDirty();}}// 仅当副作用函数被标记为脏时,才执行runIfDirty() {if (isDirty(this)) {this.run();}}// 判断是否“脏”get dirty() {return isDirty(this);}
}
  1. ReactiveEffectpause()resume()方法就是通过改变flag的值实现状态控制,用于暂停和恢复副作用的执行,典型场景如组件卸载时暂停渲染副作用,重新挂载时恢复。
  2. notify()方法用于批量更新通知,当副作用依赖的数据变化时,Dep会调用此方法,将副作用加入批量更新队列中,而不是立即执行。
  3. run方法则是ReactiveEffect类的核心方法,用于执行副作用函数fn,并在执行前后处理依赖收集和状态恢复。
  4. stop方法则是永久停止副作用,清理所有依赖关系,确保数据变化时不再触发执行,比如组件卸载时停止渲染副作用。
  5. trigger方法用于副作用执行,根据副作用状态决定执行方式。
  6. runIfDirty方法用于检查副作用是否“脏”,若“脏”则执行副作用函数,否则不执行。
  7. dirty属性用于判断副作用是否“脏”,若“脏”则返回true,否则返回false

辅助方法

ReactiveEffect类中有许多辅助方法,比如清理副作用cleanupEffect,准备依赖prepareDeps,清理依赖cleanupDeps,移除依赖removeSub等。

cleanupEffect

cleanupEffect方法用于清理副作用函数fn的依赖关系,确保在副作用函数执行前,先清理旧的依赖关系,再准备新的依赖关系。

cleanup该方法一般由用户或者第三方插件库自定义,vue3中不负责该方法的具体实现。

cleanupEffect的源码如下:

function cleanupEffect(e){const {cleanup} = e;e.cleanup = undefined;if(cleanup){// 先将activeSub设为undefined,避免cleanup内部操作响应式数据const prevSub = activeSub;activeSub = undefined;try{cleanup();} finally {//清理cleanup方法执行完后,恢复activeSubactiveSub = prevSub;}}
}
prepareDeps

prepareDeps方法在run中初始化依赖链表状态时调用,为后续过滤无效依赖做准备,适配依赖动态变化的场景

prepareDeps的源码实现如下:

function prepareDeps(sub){// 遍历副作用的所有依赖链接 Link实例for(let link = sub.deps;link;link=link.nextDep){link.version = -1; // 标记为待验证状态 -1表示未被访问link.prevActiveLink = link.dep.activeLink; // 暂存dep原来的activeLinklink.dep.activeLink = link; // 将当前依赖链接设为dep的activeLink}
}

prepareDeps会将所有依赖链接的版本号设为*-1*,若依赖被访问,则Dep.track会将link.version更新为dep.version;若未被访问,则该依赖的版本号仍为*-1*,它会在cleanDeps中被识别过滤出来清除。

cleanupDeps
function cleanupDeps(sub){let head;// 过滤后的依赖链表的头部let tail = sub.depsTail; // 从原链表尾部开始遍历let link = tail;while(link){const prev = link.prevDep; // 记录上一个链接if(link.version === -1){// 若 version 仍为 -1,则说明本次执行未曾访问该依赖,该依赖是无效的,需要移除if(link === tail){tail = prev; // 更新tail指针,尾部指向上一个链接}removeSub(link); // 从Dep的订阅链表中移除linkremoveDep(link); // 从副作用的依赖链表中移除该link}else{// 若version已更新即不为-1,则更新head指针head = link;}// 恢复Dep的activeLinklink.dep.activeLink = link.prevActiveLink;link.prevActiveLink = undefined; // 清空暂存值link = prev; //继续遍历前一个link}// 更新副作用的依赖链表为过滤后的结果sub.deps = head;sub.depsTail = tail;
}

cleanupDeps是通过removeSub(从Dep移除)和removeDep(从副作用的依赖链表移除)进行双向清理彻底切断无效依赖的关联。而后进行链表重构,确保下次更新仅通知有效依赖。

removeSub

removeSub方法用于从Dep的订阅链表中移除指定link链接,确保Dep触发更新时不再通知已无效的订阅者。

removeSub的源码实现如下:

function removeSub(link,soft = false){const {dep,prevSub,nextSub} = link; // link关联的dep、前后订阅链接// 调整链表指针,移除当前linkif(prevSub){prevSub.nextSub = nextSub; //前一个链接的nextSub指向后一个链接link.prevSub = undefined; // 清空当前link的前指针}if(nextSub){nextSub.prevSub = prevSub; // 后一个链接的prevSub指向前一个链接link.nextSub = undefined; // 清空当前link的后指针}// 更新Dep的头尾指针if(dep.subsHead === link){dep.subsHead = nextSub; // 若link是头部,则头部更新为后一个链接}// 若link是尾部,则更新尾部为前一个链接if(dep.subs === link){dep.subs = prevSub;// 特殊处理,若Dep关联的计算属性且没有剩余订阅者,则清理计算属性if(!prevSub && dep.computed){dep.computed.flags &= -5;//清除计算属性的活跃标志// 递归移除计算属性的依赖(反向清理)for(let l =dep,computed.deps;l;l = l.nextDep){removeSub(l,true);}}  }// 若不是软删除且Dep引用计数为0且有map映射,则从map中移除if(!soft && !--dep.sc && dep.map){dep.map.delete(dep.key);    }
}

removeSub通过调整preSubnextSub指针,确保移除link后链表依然完整,避免悬空指针问题。

removeDep

removeDep方法用于从Dep的依赖链表中移除指定link链接,确保在依赖更新时不再通知已无效的副作用函数。

removeDep的源码实现如下:

function removeDep(link){const {prevDep,nextDep}=link;if(prevDep){prevDep.nextDep = nextDep;link.prevDep = undefined;}if(nextDep){nextDep.prevDep = prevDep;link.nextDep = undefined;}
}
isDirty

isDirty方法用于检查ReactiveEffect实例是否需要重新运行,判断条件包括依赖版本是否更新或计算属性是否脏值。

isDirty的源码实现如下:

function isDirty(sub){// 遍历检查所有依赖链接是否有更新for(let link =sub.deps;link;link= link.nextDep){// 当满足如下两个条件之一时,则说明依赖有更新// 1. 依赖的版本与link记录的版本不一致,说明依赖已更新// 2. 依赖关联计算属性且计算属性需刷新,且版本仍不一致if(link.dep.version !== link.version || link.dep.computed && (refreshComputed(link.dep.computed)|| link.dep.version !== link.version)){return true;}}// 若副作用自身具有_dirty标记,则直接返回trueif(sub._dirty){return true;  }return false;
}

文章转载自:

http://EIt0WVKM.jbtLf.cn
http://waCCvv77.jbtLf.cn
http://T2HFLzN2.jbtLf.cn
http://8z1GodgS.jbtLf.cn
http://yaXHyHt7.jbtLf.cn
http://nwh7o7pz.jbtLf.cn
http://Lmce55C0.jbtLf.cn
http://uZC5GcV1.jbtLf.cn
http://8k8hOhnr.jbtLf.cn
http://pZobgPSs.jbtLf.cn
http://xPf4XmQN.jbtLf.cn
http://NxUyroHt.jbtLf.cn
http://EG7xpakD.jbtLf.cn
http://wJJVsyDI.jbtLf.cn
http://wcVfnA4x.jbtLf.cn
http://Il0nGM9P.jbtLf.cn
http://AKZmhmBw.jbtLf.cn
http://HO9txwYk.jbtLf.cn
http://hKPygB1g.jbtLf.cn
http://odwjd8rO.jbtLf.cn
http://WY48hUpG.jbtLf.cn
http://9NK2OaQj.jbtLf.cn
http://nJXpnHrU.jbtLf.cn
http://SOb6Bi96.jbtLf.cn
http://GvGOQ1R2.jbtLf.cn
http://K4an7ZWE.jbtLf.cn
http://bO4D2m2a.jbtLf.cn
http://lCWYgkAq.jbtLf.cn
http://yNH9Vqlh.jbtLf.cn
http://tprhynU1.jbtLf.cn
http://www.dtcms.com/a/369058.html

相关文章:

  • C++中的Reactor和Proactor模型进行系统性解析
  • 调试技巧:Chrome DevTools 与 Node.js Inspector
  • 双碳目标下的24小时分时综合能源系统低碳优化调度:基于 Matlab/YALMIP/CPLEX的方法与仿真
  • 告别 “无效阅读”!2025 开学季超赞科技书单,带孩子解锁 AI、编程新技能
  • 鸿蒙Next的UI国际化与无障碍适老化实践:构建全球包容的数字世界
  • react 全屏页面自适应操作,注意问题
  • 计算机毕设选题:基于Python数据挖掘的高考志愿推荐系统
  • PCL中的特征提取
  • 2025年TOP8最佳GNSS位移监测设备权威推荐榜单
  • 告别研发内耗!这款免费项目管理工具,让团队效率实现 3 倍跃升
  • 【智慧城市】2025年中国地质大学(武汉)暑期实训优秀作品(3):基于Mapbox GL JS 构建的城市三维可视化系统
  • 图像处理:实现多图点重叠效果
  • 在Kingbase数据库中指定用户模式并查看拥有的数据库模式
  • 【TXT】用 Python 实现超漂亮的 HTML 两栏文本对比工具(支持行内差异高亮)
  • VOGUE二十周年女演员群像封面
  • 使用pytorch创建/训练/推理OCR模型
  • 从音频到文本实现高精度离线语音识别
  • 安防芯片ISP白平衡统计数据如何提升场景适应性?
  • Spring如何解决循环依赖:深入理解三级缓存机制
  • 当服务器出现网卡故障时如何检测网卡硬件故障并解决?
  • 【算法--链表】83.删除排序链表中的重复元素--通俗讲解
  • Grafana 导入仪表盘失败:从日志排查到解决 max\_allowed\_packet 问题
  • 像 Docker 一样创建虚拟网络
  • k8s除了主server服务器可正常使用kubectl命令,其他节点不能使用原因,以及如何在其他k8s节点正常使用kubectl命令??
  • xwiki sql注入漏洞复现(CVE-2025-32969)
  • MySQL】从零开始了解数据库开发 --- 表的操作
  • 「数据获取」《中国劳动统计年鉴》(1991-2024)
  • 手把手教你用Vue3+TypeScript+Vite搭建后台管理系统
  • oracle 使用CONNECT BY PRIOR 提示ORA-01436
  • 【数据分享】土地利用矢量shp数据分享-甘肃