🌿 一文看懂 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 控制执行时机。

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


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

相关推荐
用户47949283569157 分钟前
Safari 中文输入法的诡异 Bug:为什么输入 @ 会变成 @@? ## 开头 做 @ 提及功能的时候,测试同学用 Safari 测出了个奇怪的问题
前端·javascript·浏览器
没有故事、有酒19 分钟前
Ajax介绍
前端·ajax·okhttp
朝新_22 分钟前
【SpringMVC】详解用户登录前后端交互流程:AJAX 异步通信与 Session 机制实战
前端·笔记·spring·ajax·交互·javaee
裴嘉靖25 分钟前
Vue 生成 PDF 完整教程
前端·vue.js·pdf
毕设小屋vx ylw28242627 分钟前
Java开发、Java Web应用、前端技术及Vue项目
java·前端·vue.js
冴羽1 小时前
今日苹果 App Store 前端源码泄露,赶紧 fork 一份看看
前端·javascript·typescript
蒜香拿铁1 小时前
Angular【router路由】
前端·javascript·angular.js
brzhang2 小时前
读懂 MiniMax Agent 的设计逻辑,然后我复刻了一个MiniMax Agent
前端·后端·架构
西洼工作室2 小时前
高效管理搜索历史:Vue持久化实践
前端·javascript·vue.js
广州华水科技2 小时前
北斗形变监测传感器在水库安全中的应用及技术优势分析
前端