加餐- 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、调度器、队列等模块协同工作,保证了数据驱动视图更新的高效和灵活。
相关推荐
洋流2 分钟前
什么?还没弄懂关键字this?一篇文章带你速通
前端·javascript
晴殇i3 分钟前
for...in 循环的坑,别再用它遍历 JavaScript 数组了!
前端·javascript
littleplayer5 分钟前
iOS 单元测试详细讲解-DeepSeek
前端
littleplayer6 分钟前
iOS 单元测试与 UI 测试详解-DeepSeek
前端·单元测试·测试
夜熵9 分钟前
Vue中nextTick()用法
前端·面试
小桥风满袖9 分钟前
Three.js-硬要自学系列15 (圆弧顶点、几何体方法、曲线简介、圆、椭圆、样条曲线、贝塞尔曲线)
前端·css·three.js
洋流10 分钟前
JavaScript事件流机制详解:捕获、冒泡与阻止传播
前端·javascript
啊花是条龙10 分钟前
在 Angular 中使用 ECharts 并处理 xAxis 标签的点击事件
前端·angular.js
凌冰_15 分钟前
CSS3 基础(背景-文本效果)
前端·css·css3
tjh000117 分钟前
vue3+TS 手动实现表格滚动
前端·javascript·vue.js