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

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


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

相关推荐
梦想CAD控件4 分钟前
在线CAD开发包结构与功能说明
前端·javascript·vue.js
张拭心8 分钟前
春节后,有些公司明确要求 AI 经验了
android·前端·人工智能
时光不负努力9 分钟前
typescript常用的dom 元素类型
前端·typescript
小怪点点14 分钟前
大文件切片上传
前端
时光不负努力15 分钟前
TS 常用工具类型
前端·javascript·typescript
SuperEugene16 分钟前
Vue状态管理扫盲篇:Vuex 到 Pinia | 为什么大家都在迁移?核心用法对比
前端·vue.js·面试
张拭心19 分钟前
Android 17 来了!新特性介绍与适配建议
android·前端
徐小夕23 分钟前
pxcharts-vue:一款专为 Vue3 打造的开源多维表格解决方案
前端·vue.js·github
Hilaku24 分钟前
我会如何考核一个在简历里大谈 AI 提效的高级前端?
前端·javascript·面试
青青家的小灰灰1 小时前
React 反模式(Anti-Patterns)排查手册:从性能杀手到逻辑陷阱
前端·javascript·react.js