加餐- 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 数据变更时,触发 trigger → triggerEffect
  • 若 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、调度器、队列等模块协同工作,保证了数据驱动视图更新的高效和灵活。
相关推荐
吴声子夜歌3 分钟前
HTML——结构化微数据语言简介
前端·数据库·html
TeamDev4 分钟前
JxBrowser 9.5.2 版本发布啦!
java·前端·windows·chromium·混合应用·jxbrowser·浏览器控件
鬼手点金6 分钟前
Claude Code示范案例-进阶学习路径
java·服务器·前端·学习·计算机视觉·前向传播
吴声子夜歌11 分钟前
HTML——Web Components开发
前端·html
溪语流沙32 分钟前
【每天一个CSS | Day08】整排按钮被鼠标吸走的磁吸导航
前端·css·计算机外设
就叫年华吧丶41 分钟前
长文档点目录定位总是不准?一个 content-visibility + 平滑滚动引发的连环坑(Vue3 实战)
前端·javascript·算法·vue
JudithHuang1 小时前
React 路由:React Router
前端·react.js·前端框架
赵得C1 小时前
创建 SVG 图标预览页面:从零实现到解决 CORS 问题
前端·javascript·html
IT_陈寒1 小时前
Java的HashMap线程安全问题让我深夜掉光了头发
前端·人工智能·后端