加餐- Vue3中响应式的 watch 流程实现解读【手摸手带你实现一个vue3】

大家好,我是作曲家种太阳,今天我们来手把手去看看 Vue 3 响应式系统中非常核心的一部分:watch 是怎么个执行流程和触发流程,深入了解watch的原理和设计理念

watch 是什么?

watch 是 Vue3 中用于监听响应式数据变化的 API,当被监听的数据发生变化时,它会触发用户定义的回调函数。它适用于需要在数据变化时执行异步或副作用逻辑的场景。

watch 接收三个参数:

  1. source:响应式对象、getter 函数、ref 或数组等。
  2. cb:当监听的数据变化时要执行的回调函数。
  3. options :配置项,如:
    • immediate: 是否立即执行一次 cb。
    • deep: 是否进行深度监听。

源码解读:watch 的执行过程

1️⃣ watch 函数入口

  • 源码路径:packages/runtime-core/src/apiWatch.ts
  • 核心调用的是 doWatch 方法

watch 中做了以下处理:

  • 判断 source 类型,生成 getter(一般是 () => source
  • 若为 reactive 对象,默认开启 deep 监听
  • 创建 baseGetter
  • 创建 job 函数:数据变化时触发的回调逻辑
  • 创建调度器 scheduler = () => queuePreFlushCb(job)
  • 使用 ReactiveEffect 包装 getter,并附带 scheduler
  • 返回一个 teardown 回调,用于后续停止监听

2️⃣ 响应式数据触发 setter

  • reactive 数据变更时,触发 triggertriggerEffect
  • 若 effect 有 scheduler,则执行 scheduler()
  • 此 scheduler 调用 queuePreFlushCb(job),将任务放入微任务队列

3️⃣ queuePreFlushCb 函数执行

ts 复制代码
queuePreFlushCb(job) → queueCb(job, pendingPreFlushCbs)
→ queueFlush() → Promise.resolve().then(flushJobs)

此过程核心在于:

  • 将 job 推入队列 pendingPreFlushCbs
  • 使用微任务 Promise 确保异步执行 flushJobs

4️⃣ flushJobs 函数

  • 触发 flushPreFlushCbs → 执行所有 pendingPreFlushCbs
  • job 被调用 → 触发 effect.run()

此时 effect.run() 会重新调用 getter,获取新的值。

  • 然后调用 callWithAsyncErrorHandling(cb, ...) 执行回调函数 cb
  • 参数为:(newValue, oldValue)

watch 核心组成

整个 watch 的运行分为 4 大块:

  1. watch 函数初始化逻辑
  2. reactive 的 setter 触发 trigger
  3. 调度系统:queuePreFlushCb + flushJobs
  4. job 执行,运行 effect,触发 cb 回调

调度系统实现机制

watch 中使用了 Vue3 的调度系统,该系统包含两个核心机制:

💤 lazy 懒执行

effect 设置 lazy: true 时,不会立即执行 run() 方法,而是等到手动触发或依赖变动时才执行。

🧠 scheduler 调度器

调度器控制两件事:

  1. 控制执行顺序: 利用 queue + Promise.then 实现异步控制,确保 DOM 更新前统一 flush。

  2. 控制执行规则: 例如:如果连续赋值多次,只执行一次 effect(节流/去抖)

实现关键:

ts 复制代码
export function queuePreFlushCb(cb: Function) {
  queueCb(cb, pendingPreFlushCbs)
}

function queueFlush() {
  if (!isFlushPending) {
    isFlushPending = true
    currentFlushPromise = resolvedPromise.then(flushJobs)
  }
}

function flushJobs() {
  isFlushPending = false
  flushPreFlushCbs()
}

function flushPreFlushCbs() {
  const cbs = [...new Set(pendingPreFlushCbs)]
  pendingPreFlushCbs.length = 0
  for (const cb of cbs) cb()
}

通过微任务机制控制更新节奏,实现 watch 调度的异步安全。

总结

  • watch 是用于观察响应式数据变化的 API,适合执行副作用逻辑。
  • 它底层基于 ReactiveEffect 创建 effect,并使用 scheduler 控制回调时机。
  • watch 的调度机制基于异步队列和微任务,确保回调顺序和性能。
  • Vue3 的响应式系统通过 effect、调度器、队列等模块协同工作,保证了数据驱动视图更新的高效和灵活。
相关推荐
FFF-X1 分钟前
前端调用阿里云接口语音合成演示
前端
Java中文社群1 分钟前
绝了!分享一个超赞的UI框架!
前端
Zayn11 分钟前
JavaScript 小数精度问题
前端·javascript
西维13 分钟前
高效使用AI从了解 Prompt / Agent / MCP 开始
前端·人工智能·后端
110546540129 分钟前
35、自主移动机器人 (AMR) 调度模拟 (电子厂) - /物流与仓储组件/amr-scheduling-electronics
前端·javascript
SuperYing31 分钟前
还在为调试组件库发愁吗?yalc 帮你一把
前端·npm
跟橙姐学代码38 分钟前
Python 高手都偷偷用的 Lambda 函数,你还在傻傻写 def 吗?
前端·python
Eddy38 分钟前
useEffect最详细的用法
前端
一枚前端小能手43 分钟前
🎨 用户等不了3秒就跑了,你这时如何是好
前端
Eddy1 小时前
什么时候应该用useCallback
前端