Vue3 响应式系统——computed 和 watch

news/2026/1/19 22:33:26/文章来源:https://www.cnblogs.com/haoxiugong/p/19503900

学过上一节 ref、reactive、effect 后,接下来我们探究响应式变量的使用——computed 和 watch 。

一、computed 和 watch 概述

所有响应式逻辑都会依赖 effect 执行,computed / watch / render 本质都是 effect。

  • effect:依赖 state。
  • dep:被外部 effect 依赖。
  • computed​:带缓存的、惰性的、基于依赖的派生 effect
  • watch​:主动监听数据变化的副作用调度器

一句话总结二者的由来:

副作用(watch)不可缓存,派生值(computed)必须缓存

  • computed: _dirty + scheduler
  • watch / watchEffect: 直接执行副作用

二、computed 的底层实现

2.1 computed 的核心结构

源码中 computed 返回的是一个 ​ComputedRefImpl 实例​:

class ComputedRefImpl {_valuedepeffect_dirty = true
}

可以理解为:computed = ref + effect + dirty 标记

2.2 computed 的创建流程

function computed(getter) {const cRef = new ComputedRefImpl(getter)return cRef
}

构造函数内部核心逻辑(简化):

this.effect = new ReactiveEffect(getter, () => {() => return getter(this._value), // getter,本质上就是我们 computed 调用是传递的 fn 参数() => { // setterif (!this._dirty) {this._dirty = truetriggerRefValue(this)}}
})

🚨 ​computed 自己不会重新计算,​它只会在「依赖变了(computedEffect.scheduler)」时,把 _dirty 标记为 true

而真正重新计算是在 effect() 时,触发了响应式变量的 getter(并且为脏数据),然后才计算 computedEffect.run() 。

2.3 computed.value 的 getter

get value() {trackRefValue(this)if (this._dirty) {this._dirty = falsethis._value = this.effect.run()}return this._value
}

2.3.1 依赖收集的是「computed 本身」

trackRefValue(this)

依赖首先收集的不是 track getter 里的响应式数据,而是:“谁用到了这个 computed”

2.3.2 computed 是惰性执行的

if (this._dirty) {this._value = this.effect.run()
}
  • 依赖变了不会立刻重新计算
  • 只有 ​.value​ 被访问时才重新算

2.3.3 computed 一定有缓存

this._dirty = false
return this._value

只要依赖没变,多次访问 computedValue.valuegetter 只执行一次。

2.4 computed 的完整执行链路

state.a 改变↓
computed.effect.scheduler 执行↓
_dirty = true(不会立刻算)↓
下一次访问 computed.value↓
effect.run() → 重新计算(“被动”计算)

2.5 computed 结合响应式变量的执行过程

2.5.1 过程概述

const state = reactive({ a: 1 })const c = computed(() => state.a + 1)effect(() => {console.log(c.value)
})
state.a++↓
trigger state.a.dep↓
computed.effect.scheduler↓
computed._dirty = true↓
trigger computed.dep↓
render effect run↓
computed.value 被访问↓
computed.effect.run()  ← 真正计算

看似很复杂,其实就是一个 副作用收集触发的嵌套逻辑。

​🫡一句话总结:​全局 effect 执行,收集依赖“响应式变量”(computed 变量 + ref/reactive 变量),然后 computed 响应式变量又依赖于 ref/reactive 变量,把他们收集到 computed 变量自身的 effect 中。然后一旦 ref/reactive 变量更新,那么首先触发自身 trigger 更新,然后被依赖的 computed effect 的 trigger 更新,进而最终的 computed 变量更新(“懒更新”:用到的时候才 run)。

2.5.2 过程讲解

computed 和 ref/reactive 实例初始化过程之前已经详细讲解过,这里不再重复讲解。

computed 依赖收集过程:

  1. effect 执行,访问 computed.value
effect↓
computed.dep.add(effect)
  1. computed.effect.run(),触发 computed 的 getter,进而触发 state(.a) 的 getter
state.a↓
computed.effect↓
computed.dep↓
effect
  1. state.a 改变,触发 state.a 的 trigger
trigger(target, 'a')
=>  dep = state.a.dep
=>  computed.effect
  1. 执行 computed.effect.scheduler(此时并不是 run!)

内部执行 scheduler 的时候,之后会回头执行 _effect.run() ,也就相当于执行了 fn() 。

scheduler = () => {if (!this._dirty) {this._dirty = true // 打标记triggerRefValue(this) // 非 lazy 下才能在这里内部执行 run}
}
  1. triggerRefValue(this) 等价于
computed.dep.forEach(effect => effect.run()) // 通知“谁依赖 computed”
  1. render effect 重新执行,再次访问 computed.value
effect(() => {console.log(c.value)
})
// 然后才访问 computed.value
if (this._dirty) {this._value = this.effect.run() // computed 这时候才真正重新计算
}

2.5.3 误区解答

  1. 为什么 computed 不直接 run?
❌ 如果 state 改一次,computed 立刻算一次:
多个 state 连续变更 → 重复计算
computed 可能根本没人用
✅ 延迟到 .value 访问:
惰性
合并更新
性能最优
  1. 为什么 computed 需要自己的 dep?
effect(() => c.value)
如果 computed 没有 dep:
外部 effect 无法被触发
computed 更新无法传播
computed 本身就是一个“可依赖对象”

2.6 computed 为什么不直接做副作用?

computed(() => {console.log(state.count)
})

effect 是副作用函数,而 computed 相较于 effect 的区别:

  • computed 可能永远不执行
  • computed 可能被缓存
  • computed 只保证 value 正确,不保证副作用执行

因此,我们不能直接把 computed 作为副作用函数使用。

三、watch 的底层实现

3.1 watch 的本质

watch = 手动创建 effect + 自定义 scheduler

Vue3 中所有 watch/watchEffect 最终都会走到:

doWatch(source, cb, options)

3.2 watch 的 effect 是“非惰性”的

const effect = new ReactiveEffect(getter, scheduler)
  • watch 的 effect 默认立刻收集依赖
  • 后续只要依赖变,就进入 scheduler

3.3 watch 的 getter 是怎么生成的?

情况一:watch ref

watch(count, cb)

getter 实际是:

() => count.value

情况二:watch reactive

watch(state, cb)

getter 实际是:

() => traverse(state) // 深度(deep)监听每一个属性,强制触发所有 getter → 收集所有依赖

3.4 watch 的 scheduler(真正执行 cb)

const job = () => {const newValue = effect.run()cb(newValue, oldValue) // 在下次触发更新时,对回调函数做调度执行oldValue = newValue
}

调度时机由 flush 决定:

3.5 watch vs watchEffect 的区别

非常建议直接看源码,这里 AI 由于不了解源码,经常会产生一些误导性的“发言”。

watchEffect 本质是:没有回调函数,只有 source(fn) 的 watch。

watch 的副作用不是 getter,而是 cb

job = () => {const newValue = effect.run()cb(newValue, oldValue)
}

依赖收集和副作用执行:

watch(() => state.a,(newVal, oldVal) => { /* 副作用 */ }
)
依赖收集:
effect.run()↓
执行 getter:() => state.a↓
track(state, 'a')副作用执行:
state.a 改变↓
trigger↓
scheduler↓
job()↓
cb(newVal, oldVal)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1185425.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CC++核心介绍

C 核心介绍C 是由 Bjarne Stroustrup 在 1980 年代基于 C 语言扩展而来的编程语言,核心特点是兼容 C 语言且新增了面向对象编程(OOP)特性,同时保留了对底层硬件的直接操控能力,因此被称为 “混合范式语言”—— 既支持…

历年CSP-J初赛真题解析 | 2014年CSP-J初赛

​欢迎大家订阅我的专栏:算法题解:C++与Python实现! 本专栏旨在帮助大家从基础到进阶 ,逐步提升编程能力,助力信息学竞赛备战! 专栏特色 1.经典算法练习:根据信息学竞赛大纲,精心挑选经典算法题目,提供清晰的…

HarmonyOS 中如何避免线程阻塞?从原理到实战的完整解析

摘要 随着 HarmonyOS / OpenHarmony 在手机、平板、智慧屏、车机等多设备上的落地,应用的复杂度正在明显提升。页面不再只是简单展示,而是伴随着网络请求、数据计算、设备协同等大量逻辑。如果这些逻辑处理不当,很容易出现页面卡顿、点击无响…

中华老字号的现代传承:神象人参粉,以科技赋能千年滋补智慧 - 行业调研院

在快节奏的现代生活中,人们对健康的追求从未停歇,尤其渴望一种既能承袭传统智慧、又经得起科学验证的高品质滋补品。今天,我们为您深入解读的,正是这样一款融合了百年信誉与现代科技的典范之作——神象人参粉。它不…

4 个值得关注的开源业务数据管理工具

从业务系统视角梳理数据管理的核心问题,对比分析 NocoBase、Directus、Budibase 与 Appsmith 在建模、权限、流程与扩展性上的差异与适用场景。原文链接:https://www.nocobase.com/cn/blog/4-open-source-data-manag…

嵌入式系统设计师软考个人笔记<2>

一、嵌入式硬件核心组件1. 处理器与可编程逻辑器件处理器:MCU(微控制器):集成 CPU 存储器 外设(如 STM32、51 单片机),是嵌入式系统的核心;MPU(微处理器)&a…

【投票邀请】助力我冲击CSDN 2025博客之星TOP138,每一票都藏着你的偏爱~

【投票邀请】助力我冲击CSDN 2025博客之星TOP138,每一票都藏着你的偏爱~ 亲爱的小伙伴们、长期陪伴我的博粉家人们: 大家好呀~ 今天怀着满满的诚意和一点点小紧张,来向大家发出一个特别的邀请!我正在参加…

c+++核心介绍

C 核心介绍C 是由 Bjarne Stroustrup 在 1980 年代基于 C 语言扩展而来的编程语言,核心特点是兼容 C 语言且新增了面向对象编程(OOP)特性,同时保留了对底层硬件的直接操控能力,因此被称为 “混合范式语言”—— 既支持…

vLLM 大模型性能测试

目录短文本测试中等长度测试长文本测试 短文本测试 vllm bench serve --model /data/models/Qwen1.5-14B-Chat-AWQ --backend vllm --base-url http://localhost:8000 --endpoint /v1/completions --dataset-name…

C++2026核心介绍

C 核心介绍C 是由 Bjarne Stroustrup 在 1980 年代基于 C 语言扩展而来的编程语言,核心特点是兼容 C 语言且新增了面向对象编程(OOP)特性,同时保留了对底层硬件的直接操控能力,因此被称为 “混合范式语言”—— 既支持…

《Python模糊测试普及困局:隐性壁垒与破局路径深度解析》

Python生态的生命力源于其极致的灵活性与丰富的库资源,这种特性让开发者能快速搭建各类应用、适配多元场景,却也为模糊测试的普及埋下了深层矛盾。模糊测试的核心价值在于通过非预设输入的探索性验证,捕捉常规测试难以触及的隐性风险&#xf…

机器学习实战:多项式回归建模——从模拟数据到模型评估

博主正在参加CSDN博客之星评选,需要您的支持! 投票链接:https://www.csdn.net/blogstar2025/detail/056 题目:多项式回归建模练习 1. 训练资料生成 给定函数:y sin(x) 取样:在给定的 x 值(x…

英特尔AI双赛走出的万名开发者,正在弥合AI人才缺口

作者:金旺近日,据央视新闻报道,我国人工智能企业数量已突破6200家,2025年我国人工智能核心产业规模已经突破1万亿元,人工智能大模型正在融入千行百业。与此同时,另一个来自人力资源社会保障部的统计数据显示…

【网安区块链项目参考】基于区块链的安全日志防篡改系统的设计与实现

整个系统资料包含:全套源码注释开发文档部署指导说明,需要的话可以私信博主获取,伸手党勿扰基于区块链的安全日志防篡改系统的设计与实现摘要随着信息系统规模的不断扩大,系统安全日志在网络安全审计、入侵检测与事后取证中发挥着…

【计算机毕业设计案例】基于django定制化ERP系统APP企业客户设备进销存系统小程序(程序+文档+讲解+定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

无线网络仿真:蓝牙网络仿真_(15).蓝牙网络仿真研究前沿

蓝牙网络仿真研究前沿 1. 蓝牙网络仿真的背景与意义 1.1 无线网络仿真的重要性 无线网络仿真是一种重要的研究工具,用于评估和优化无线通信系统的性能。通过仿真,研究人员可以模拟真实世界中的各种无线通信场景,从而分析网络的行为和性能。无…

用提示工程让大模型自己检查自己:CoVe方法有效减少幻觉

LLM幻觉问题至今没有根治方案。RAG能缓解一部分,但成本高、架构复杂,而且只适用于有外部知识源的场景。而对于模型"应该知道但经常搞错"的那类问题,比如历史事件的时间线、人物履历的细节,RAG帮不上什么…

Flink 流处理从入门到精通:DataStream 转换与窗口操作实战

💡 Flink DataStream API 实战全解析:Map、FlatMap、Filter 与 Window 窗口操作详解 封面图建议:深蓝科技感背景,中央 Flink logo,流动线条展示数据流动,关键词“Map / FlatMap / Filter / Window Processing”,简洁直观。 目录 Flink 执行环境与时间语义 基础转换操作…

Java 27 首获后量子混合密钥交换功能提案

计划于9月发布的标准Java版本——Java开发工具包(JDK)27已经获得了首个功能提案:后量子混合密钥交换能力,用于加强网络安全。该功能名为"TLS 1.3的后量子混合密钥交换",已于1月13日在OpenJDK的JDK 27页面上列…

深入 Flink 数据源:RichSourceFunction 的设计与最佳实践

Flink DataStream API 实战:从 SourceFunction 到 RichSourceFunction 的进阶与优化 本文详细讲解了 Apache Flink 中源函数(Source Function)的演进,从最基础的 SourceFunction 到功能更强大的 RichSourceFunction,并结合数据库、Kafka 仿真及可配置化场景提供实战示例,…