news 2026/5/7 17:55:12

Vue3 响应式系统——computed 和 watch

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 { _value dep effect _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 参数 () => { // setter if (!this._dirty) { this._dirty = true triggerRefValue(this) } } })

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

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

2.3 computed.value 的 getter

get value() { trackRefValue(this) if (this._dirty) { this._dirty = false this._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)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/6 6:36:10

Speech Seaco快速入门:3步实现录音转文字,小白必看

Speech Seaco快速入门:3步实现录音转文字,小白必看 你是不是也遇到过这样的问题?辛辛苦苦剪辑好的视频,上传到不同平台时却发现——没有字幕,播放量直接打折扣。尤其是抖音、快手、B站这些短视频平台,用户…

作者头像 李华
网站建设 2026/4/23 11:09:00

AI智能文档扫描仪实战指南:法律文书安全扫描本地化部署

AI智能文档扫描仪实战指南:法律文书安全扫描本地化部署 1. 引言 1.1 场景需求与痛点分析 在法律、金融、审计等对数据隐私要求极高的行业中,日常工作中频繁涉及合同、诉状、证据材料等敏感文件的数字化处理。传统云服务类扫描应用(如“全能…

作者头像 李华
网站建设 2026/4/29 4:17:16

OrCAD Capture集成Pspice安装操作指南

从零构建电路仿真环境:OrCAD Capture集成Pspice实战指南 你有没有遇到过这种情况?花了一个小时画好了一个精密的LDO原理图,信心满满地点开“仿真”按钮——结果弹出一条红色警告:“Pspice not available” 或者 “License checko…

作者头像 李华
网站建设 2026/5/3 23:07:01

阿里通义轻量模型:CosyVoice-300M Lite技术详解

阿里通义轻量模型:CosyVoice-300M Lite技术详解 1. 引言 1.1 背景与挑战 随着语音合成(Text-to-Speech, TTS)技术在智能客服、有声阅读、虚拟助手等场景的广泛应用,对模型部署效率和资源消耗的要求日益提高。传统TTS模型往往依…

作者头像 李华
网站建设 2026/5/2 23:18:13

轻量级BERT模型应用:移动端部署实战

轻量级BERT模型应用:移动端部署实战 1. 引言 随着自然语言处理技术的不断演进,BERT(Bidirectional Encoder Representations from Transformers)已成为语义理解任务的核心架构之一。然而,原始BERT模型通常参数庞大、…

作者头像 李华
网站建设 2026/4/17 22:27:42

SGLang一键部署方案:免环境配置快速启动教程

SGLang一键部署方案:免环境配置快速启动教程 SGLang-v0.5.6 是当前稳定版本,具备完整的推理优化能力与结构化生成支持。本文将围绕该版本,详细介绍如何通过一键部署方式快速启动 SGLang 服务,无需繁琐的环境配置,帮助…

作者头像 李华