🌿 一文看懂 Vue 3 的 watch 源码:从原理到流程

详细源码注释

Vue 的 watch,其实就是一个"观察者"。

它悄悄盯着某个响应式数据,一旦它变了,就执行一段你写好的逻辑。

比如:

javascript 复制代码
watch(count, (newVal, oldVal) => {
  console.log('count 改了!', newVal, oldVal)
})

这个逻辑在源码里是怎么实现的呢?我们一步步来看。


🧠 一、核心思想(一句话总结)

watch = 让一个函数自动在依赖变化时重新执行。

也就是说,Vue 会:

  1. 找出你依赖了哪些响应式数据;
  2. 把这些数据的变化和一个回调函数"绑定";
  3. 一旦依赖变动 → 回调自动执行。

⚙️ 二、watch 在干什么?(通俗讲解)

我们先从用户角度看你调用 watch 时到底发生了什么:

你做的事情 Vue 内部在做的事情
传入一个 source(ref、reactive 或函数) 把它转换成一个能"取值"的 getter 函数
提供一个回调 cb 当 getter 的依赖变化时,执行这个回调
设置选项(如 deep, immediate 控制是否深度追踪、是否立即执行一次
调用 watch Vue 创建一个 "反应函数"(ReactiveEffect) 来追踪依赖
数据变化 ReactiveEffect 被触发 → 调用你的回调

一句话总结:

Vue 创建了一个专门盯着你"source"的小助手,一旦 source 的依赖变化,它就会自动执行你给的回调。


🧩 三、主要步骤详解(配图理解)

🥇 第一步:把 source 变成能取值的函数 getter

Vue 会根据 source 类型决定"怎么取值":

source 类型 getter 做的事
ref 返回 .value
reactive 对象 遍历所有属性(深度追踪)
函数 直接执行函数
数组 依次处理数组里的每个 ref/reactive

👉 目的:无论用户传什么类型,Vue 都能得到一个统一的"取值函数 getter"。


🥈 第二步:包成一个 ReactiveEffect

ini 复制代码
effect = new ReactiveEffect(getter)

这个对象可以理解为一个"反应式执行器":

  • 当依赖变化时,它会自动重新运行 getter
  • 它内部帮你完成了依赖收集、变化触发;
  • 每次重新运行时,Vue 就能检测新旧值。

💡可以理解为:

"ReactiveEffect 就是 Vue 的大脑,记得谁依赖了谁。"


🥉 第三步:定义一个 job(变化时要做的事情)

scss 复制代码
const job = () => {
  const newValue = effect.run()   // 再次执行 getter 得到新值
  if (hasChanged(newValue, oldValue) || deep) {
    cleanup?.()                   // 执行上一次注册的清理函数
    cb(newValue, oldValue, onCleanup)  // 调用用户的回调
    oldValue = newValue
  }
}

它的作用就是:

  1. 拿到新值;
  2. 比较和旧值是否不同;
  3. 如果不同,就执行回调;
  4. 更新旧值。

🧼 第四步:注册清理函数

Vue 允许你在 watch 回调里注册一个清理函数,比如:

scss 复制代码
watch(data, (newVal, oldVal, onCleanup) => {
  const timer = setTimeout(...)
  onCleanup(() => clearTimeout(timer))
})

源码里:

rust 复制代码
onWatcherCleanup(fn, false, effect)

在下一次执行 cb 之前,Vue 会自动执行上次注册的清理逻辑。


🕹️ 第五步:调度执行与控制

  • Vue 支持自定义调度(scheduler):

    • 让回调在 "微任务" 或 "渲染后" 执行;
  • 还支持:

    • immediate:立刻执行一次;
    • once:只执行一次;
    • pause() / resume() / stop():手动控制监听状态。

🪄 四、整体流程图

vbnet 复制代码
watch(source, cb, options)
 ├─► Step 1: 构造 getter(根据 source 类型)
 ├─► Step 2: 创建 ReactiveEffect(追踪依赖)
 ├─► Step 3: 定义 job(变化时要执行的函数)
 ├─► Step 4: 注册清理函数(下一次执行前清理旧逻辑)
 ├─► Step 5: 配置 scheduler(决定执行时机)
 ├─► Step 6: 初始化执行(是否 immediate)
 └─► Step 7: 返回控制句柄(pause/resume/stop)

🧾 五、总逻辑表(更易读版)

阶段 关键逻辑 作用 举例说明
1️⃣ 构造 getter 统一取值方式 无论是 ref、reactive 还是函数,都能被监听 监听 count.valueobj.a 都行
2️⃣ 创建 ReactiveEffect 追踪依赖 当依赖数据变化时自动触发 count.value → 回调自动执行
3️⃣ 定义 job 回调逻辑封装 比较新旧值、执行回调 打印新旧值
4️⃣ 注册清理函数 回调前清理上一次副作用 清除定时器、取消请求 典型:watch 中用 onCleanup()
5️⃣ 调度执行 控制何时运行回调 可延后执行 flush: 'post'
6️⃣ 初始化执行 immediate/once 是否立即执行、只执行一次 页面加载立刻监听
7️⃣ 返回控制接口 控制 watch 状态 暂停、恢复、停止监听 动态控制监听开关

🪶 六、总结一句话

watch 的本质:
Vue 帮你建了一个反应式"监听器",一旦依赖变动,它就自动重新执行。

🔹 它通过:

  • ReactiveEffect 实现依赖追踪;
  • getter 统一不同数据类型;
  • job 封装执行逻辑;
  • onWatcherCleanup 清理副作用;
  • scheduler 控制执行时机。

从而形成一个完整、灵活、可控的监听系统。


本文内容由人工智能生成,仅供学习与参考使用,请在实际应用中结合自身情况进行判断。

相关推荐
繁依Fanyi3 小时前
让工具说话:我在 Inspira Board 里用 AI 把“能用、好用、可复用”落成了日常
前端
weixin_456904274 小时前
C# 中的回调函数
java·前端·c#
kura_tsuki4 小时前
[Web网页] LAMP 架构与环境搭建
前端·架构
yinuo4 小时前
UniApp+Vue3多分包引入同一 npm 库被重复打包至 vendor 的问题分析与解决
前端
码界奇点5 小时前
Spring Web MVC构建现代Java Web应用的基石
java·前端·spring·设计规范
yinuo5 小时前
UniApp + Vue3 使用 marked 报错:SyntaxError /[\p{L}\p{N}]/u 问题分析与解决
前端
大前端helloworld5 小时前
前端梳理体系从常问问题去完善-框架篇(Vue2&Vue3)
前端·javascript·面试
小墨宝6 小时前
web前端学习LangGraph
前端·学习
南囝coding6 小时前
React 19.2 重磅更新!这几个新特性终于来了
前端·react.js·preact