《Vue3 从入门到大神35篇》Vue3 源码详解(五):effect 依赖收集原理——track 与 trigger 是如何工作的?

发布时间:2026/11/1 18:41:00

《Vue3 从入门到大神35篇》Vue3 源码详解(五):effect 依赖收集原理——track 与 trigger 是如何工作的? 本篇关键词effect、effectStack、Dep、cleanup、依赖清理、嵌套 effect、调度雏形难度等级⭐⭐⭐⭐⭐工程价值这是整个 Vue3 响应式系统的“心脏”。读懂这一篇你不仅能彻底理解 computed、watch、组件更新的本质还能从根本上规避“依赖残留”“无限循环”等高级 Bug。一、开篇为什么 effect 是响应式的灵魂在 Vue3 中一切副作用都源于effectcomputed→ 特殊的effectwatch/watchEffect→ 包装后的effect组件渲染 → 一个巨大的componentEffect一句话定义effect 是“可被重新执行的副作用函数”是数据与视图之间的桥梁。二、回顾依赖收集的三角关系我们在第三篇手写过这个结构target → key → dep → effect但在 Vue3 源码中这个关系更加严谨WeakMaptarget, Mapkey, Dep Dep SetReactiveEffect而effect本身并不只是一个函数而是一个对象。三、ReactiveEffecteffect 的真实身份在packages/reactivity/src/effect.ts中核心类是class ReactiveEffectT any { active true deps: Dep[] [] // 记录我依赖了哪些 Dep parent: ReactiveEffect | null null constructor( public fn: () T, public scheduler?: EffectScheduler ) {} run() { // 核心逻辑 } }为什么 deps 是一个数组因为一个effect可能依赖多个响应式属性effect(() { console.log(state.a state.b) })deps同时包含a和b对应的Dep。四、effectStack解决嵌套 effect 的关键1️⃣ 为什么会有嵌套 effecteffect(() { console.log(parent) effect(() { console.log(child) }) })Vue 组件渲染本身就是一个嵌套结构App.vue ├─ CompA.vue │ └─ CompB.vue每一层渲染都是一个effect。2️⃣ effectStack 的实现let activeEffect: ReactiveEffect | null null const effectStack: ReactiveEffect[] [] class ReactiveEffect { run() { if (!this.active) return this.fn() if (!effectStack.includes(this)) { try { effectStack.push((activeEffect this)) return this.fn() } finally { effectStack.pop() activeEffect effectStack[effectStack.length - 1] || null } } } }✅关键点activeEffect永远指向“当前正在执行的 effect”finally确保即使报错也能恢复栈这就是 Vue3 能正确处理嵌套渲染的根本原因。五、track依赖收集的完整流程1️⃣ track 的触发时机// reactive get 拦截中 get(target, key) { track(target, TrackOpTypes.GET, key) return Reflect.get(...) }2️⃣ track 源码精读export function track(target: object, type: TrackOpTypes, key: unknown) { if (!activeEffect) return let depsMap targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap new Map())) } let dep depsMap.get(key) if (!dep) { depsMap.set(key, (dep new Set())) } if (!dep.has(activeEffect)) { dep.add(activeEffect) activeEffect.deps.push(dep) } }重点拆解✅双向记录Dep记录effecteffect.deps记录Dep为什么要双向记录为了后续清理依赖。六、trigger依赖触发的完整流程1️⃣ trigger 的触发时机set(target, key, value) { trigger(target, TriggerOpTypes.SET, key) return Reflect.set(...) }2️⃣ trigger 源码精读简化版export function trigger(target: object, type: TriggerOpTypes, key: unknown) { const depsMap targetMap.get(target) if (!depsMap) return const effects new SetReactiveEffect() const add (effectsToAdd?: Dep) { effectsToAdd?.forEach(effect effects.add(effect)) } add(depsMap.get(key)) effects.forEach(effect { if (effect.scheduler) { effect.scheduler() } else { effect.run() } }) }✅核心逻辑找到 key 对应的所有 effect如果有 scheduler → 交给调度器否则 → 立即执行这就是 computed、watch、组件更新的统一入口。七、依赖清理cleanupVue3 相比 Vue2 的巨大进步1️⃣ Vue2 的经典 Bugif (state.show) { console.log(state.msg) }当state.show从true→falseVue2仍然会收集state.msg的依赖造成依赖残留2️⃣ Vue3 的解决方案cleanupEffectfunction cleanupEffect(effect: ReactiveEffect) { const { deps } effect for (let i 0; i deps.length; i) { deps[i].delete(effect) } effect.deps.length 0 }在effect.run()之前调用run() { cleanupEffect(this) effectStack.push((activeEffect this)) try { return this.fn() } finally { effectStack.pop() activeEffect effectStack[effectStack.length - 1] || null } }✅效果每次执行前清空旧依赖执行后重新收集新依赖完美适配if / switch / 三元表达式这是 Vue3 响应式系统“自愈能力”的来源。八、调度器雏形为什么 Vue3 不会频繁更新1️⃣ 问题场景state.count state.count state.count你不希望触发三次更新。2️⃣ scheduler 的作用class ReactiveEffect { constructor( public fn: () T, public scheduler?: EffectScheduler ) {} } run() { if (this.scheduler) { this.scheduler(this) } else { this.fn() } }组件渲染 effect 的 scheduler将更新推入微任务队列合并多次更新实现nextTick下一篇讲 watch / computed 时会深入九、调试实战用断点验证全流程在 VSCode 中依次在这些位置打断点packages/reactivity/src/effect.ts ├── ReactiveEffect.run() ├── track() ├── trigger() └── cleanupEffect()然后运行const state reactive({ ok: true, text: hello }) effect(() { console.log(state.ok ? state.text : no) }) state.ok false state.text world观察cleanupEffect是否被调用effect.deps如何变化trigger中effects的数量你会发现Vue3 的依赖系统是“动态重构”的。十、面试追问为什么 effect.deps 是数组而不是 Set因为同一个 Dep 可能被多个 key 共享数组更利于 cleanup。Vue3 如何避免死循环activeEffect在执行期间不会被重复加入 Dep。computed 和 effect 的区别computed 是懒执行 缓存 脏检查的 effect。为什么 cleanup 要在 run 之前防止旧依赖污染新依赖避免内存泄漏。十一、本篇总结✅effect不是函数而是对象✅effectStack解决嵌套执行问题✅track双向记录为 cleanup 做准备✅cleanupEffect是 Vue3 响应式“自愈”的关键✅scheduler是性能优化的入口一句话升华Vue3 的响应式系统不是“收集一次依赖就结束”而是每一次执行都是一次依赖的重建。十二、下一篇预告《Vue3 源码详解六computed 源码分析——懒计算与缓存机制的实现》
延伸阅读

更多相关文章

2026/11/1 18:39:59

TAICHI-flet终极指南:如何快速解决99%的常见问题

TAICHI-flet终极指南:如何快速解决99%的常见问题 【免费下载链接】TAICHI-flet 基于flet的一款windows桌面应用,实现了浏览图片、音乐、小说、漫画、各种资源的功能。 项目地址: https://gitcode.com/GitHub_Trending/ta/TAICHI-flet TAICHI-flet…

2026/11/1 0:18:59

健康管理:跳出认知误区看本质

健康管理:跳出认知误区看本质第一,拆解本质,定底层逻辑 健康管理的本质并非大众认知中的“标准化养生套餐”,而是基于个体信息的系统性健康维护过程。根据行业普遍认知,影响健康的因素构成复杂系统:遗传因素…

2026/10/30 0:33:42

Tinygrad终极指南:轻量级深度学习框架的10个核心优势

Tinygrad终极指南:轻量级深度学习框架的10个核心优势 【免费下载链接】tinygrad You like pytorch? You like micrograd? You love tinygrad! ❤️ 项目地址: https://gitcode.com/GitHub_Trending/tiny/tinygrad 你知道吗?在深度学习框架的世…

2026/11/1 2:01:37

1.c++入门篇

1.c中的关键字asmdoifreturntrycontinueautodoubleinlineshorttypedefforbooldynamic_castintsignedtypeidpublicbreakelselongsizeoftypenamethrowcaseenummutablestaticunionwchar_tcatchexplicitnamespacestatic_castunsigneddefaultcharexportnewstructusingfriendclassext…

2026/11/1 2:01:37

《江灯合声》:有可行的分框版,也仍可以选择连续鸣谢

武汉江岸区栎坡合唱团在免费节目册《江灯合声》中,以表格集中鸣谢资助者。各家共同要求改为独立小框,团里仍想保留表格。现有全名与支持事项清楚可见,双方此前未承诺具体排版。我支持保留表格。补充的试排材料让选择更具体:分框能…

2026/11/1 2:01:37

关于“贾子猜想”的数学地位、结构特征与讨论边界

关于“贾子猜想”的数学地位、结构特征与讨论边界摘要 本文对“贾子猜想”进行基于公开表述与现有数学事实的分析。该猜想主张:当正整数 n3m>5(即 n6,9,12,15,…)时,方程x₁ⁿ x₂ⁿ ⋯ xₙⁿ yⁿ不存在正整数解。文章首先…

2026/11/1 2:01:37

启动报Port 8088 was already in use?先查是谁占的

启动报Port 8088 was already in use?先查是谁占的 项目启动报 Port 8088 was already in use,意思很直白:这个端口已经有人占了。 先说原理:端口是"门牌号",一间房只住一个进程 端口就是一台机器上的门牌号…

2026/11/1 2:01:37

担心文档上云?本地运行的办公AI才敢放心用

单位上周开了个会,主题是"AI 提效",散会时领导补了一句:涉及客户资料的文档,不许往云端传。这话一出,会议室里本来跃跃欲试的同事全蔫了——大家平时用的那些在线 AI,文档一上传就不知道存哪儿去…

2026/11/1 1:56:37

华为OD机试真题 新系统 2026-10-28 JavaGoC【社交网络消息转发】

目录 题目 思路 Code 题目 题目内容: 在一个社交网络中,有 N 个用户(编号 0 到 N−1);每个用户都有一个「转发名单」(整数),该数二进制格式中的第 i 位为 1 表示此用户会把消息转发给第 i 个用户。 给定一个整数数组 forwardLists 和一个整数 initialSenders,请…

2026/11/1 0:01:26

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/1 1:01:33

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/1 0:01:26

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/1 1:01:33

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/1 0:01:26

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/1 1:01:33

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑