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

Vue3 响应式系统——computed 和 watch

学过上一节 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.jsqmd.com/news/269803/

相关文章:

  • CC++核心介绍
  • HarmonyOS 中如何避免线程阻塞?从原理到实战的完整解析
  • 历年CSP-J初赛真题解析 | 2014年CSP-J初赛
  • 中华老字号的现代传承:神象人参粉,以科技赋能千年滋补智慧 - 行业调研院
  • 4 个值得关注的开源业务数据管理工具
  • 用提示工程让大模型自己检查自己:CoVe方法有效减少幻觉
  • c+++核心介绍
  • C++2026核心介绍
  • 《Python模糊测试普及困局:隐性壁垒与破局路径深度解析》
  • 机器学习实战:多项式回归建模——从模拟数据到模型评估
  • 英特尔AI双赛走出的万名开发者,正在弥合AI人才缺口
  • 【计算机毕业设计案例】基于django定制化ERP系统APP企业客户设备进销存系统小程序(程序+文档+讲解+定制)
  • 无线网络仿真:蓝牙网络仿真_(15).蓝牙网络仿真研究前沿
  • Flink 流处理从入门到精通:DataStream 转换与窗口操作实战
  • 深入 Flink 数据源:RichSourceFunction 的设计与最佳实践
  • 小程序毕设项目推荐-基于django+小程序的工厂定制化ERP办公系统APP小程序【附源码+文档,调试定制服务】
  • 云厂商与软件供应商承担万亿美元AI投资,但长期成本终将转向用户
  • 2026校招薪资报告:AI/大模型岗位领跑,附完整学习路径与资料包
  • MySQL + MQ 最终一致性终极方案:Outbox + 幂等 + 补偿 + 对账全解析
  • 小程序计算机毕设之基于django智能制造业ERP系统定制化ERP系统APP小程序(完整前后端代码+说明文档+LW,调试定制等)
  • 计算机小程序毕设实战-基于django定制化ERP系统APP小程序员工管理、客户管理、设备管理【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • PHP vs Python:开发者终极选择指南
  • Web应用防火墙(WAF)核心功能特性汇总
  • 国产化建设:从“可替代”走向“可控可演进”
  • DEMO:Canal实时同步MySQL内容到Elasticsearch
  • 什么是6S?一张图讲清整理、整顿、清扫、清洁、素养、安全
  • “微型应用“兴起:非开发者自主开发应用而非购买现成产品
  • “微型应用“兴起:非开发者自主开发应用而非购买现成产品
  • 【毕业设计】基于django定制化ERP系统APP小程序(源码+文档+远程调试,全bao定制等)
  • 【课程设计/毕业设计】基于django的企业定制化ERP办公系统APP小程序【附源码、数据库、万字文档】