关于vue3 watch 第一个参数源码内部的处理方式

以下是 Vue 3 watch 第一个参数的一切形式和最终转换形态的完整解析:


核心结论

在 Vue 3 中,watch 第一个参数的所有形式最终都会被统一转换成

  1. 一个 getter 函数(当侦听单个来源时)
  2. 由 getter 函数组成的数组(当侦听多个来源时)

这是通过 Vue 源码中的 doWatch 方法完成的标准化转换。


各类形式的实际转换

1. Ref 引用

const count = ref(0)

// 🌟 转换过程:
watch(count, callback) → 被转换为:
watch(() => count.value, callback)

2. Reactive 对象

const state = reactive({ a: 1 })

// 🌟 转换过程:
watch(state, callback) → 被转换为:
watch(() => state, callback, { deep: true })

Vue 会自动给响应式对象附加 deep: true 选项。

3. 计算属性

const double = computed(/* ... */)

// 🌟 转换过程:
watch(double, callback) → 被转换为:
watch(() => double.value, callback)

4. Getter 函数

watch(
  () => obj.a + obj.b, 
  callback
)
// 🌟 直接作为 getter 函数使用,无需转换

5. 数组形式

watch(
  [refA, () => obj.b, reactiveObj],
  callback
)
// 🌟 转换后的等效形式:
[
  () => refA.value,        // ref → .value
  () => obj.b,             // getter 直接保留
  () => reactiveObj        // reactive自动+deep
]
// 并给整个 watch 添加 deep: true(因包含 reactive 对象)

源码级验证

vue@3.4.27doWatch 函数为例(关键片段):

处理入口

文件位置:packages/runtime-core/src/apiWatch.ts

function doWatch(
  source: WatchSource | WatchSource[] | object, // 接受所有可能的来源类型
  cb: WatchCallback | null,
  options: WatchOptions
) {
  // 关键转换逻辑...
}

类型分支处理

// 分支 1:处理响应式对象(Reactive)
if (isReactive(source)) {
  getter = () => source;
  deep = true; // 🔥自动启用深度侦听
}

// 分支 2:处理 Ref
else if (isRef(source)) {
  getter = () => source.value;
}

// 分支 3:处理函数(Getter)
else if (isFunction(source)) {
  getter = source;
}

// 分支 4:处理数组类型
else if (isArray(source)) {
  getter = () =>
    source.map(s => {
      if (isRef(s)) {
        return s.value;
      } else if (isReactive(s)) {
        return traverse(s); // 🔥遍历并深度响应
      } else if (isFunction(s)) {
        return s();
      }
    });
} 

// 分支 5:错误类型处理
else {
  warn('Invalid watch source');
}

转换过程可视化

原始类型              ⇒ 标准化后的 getter 形式
───────────────────────────────────────────────
ref                ⇒ () => ref.value
reactive           ⇒ () => reactiveObj + deep=true
computed           ⇒ () => computed.value
function           ⇒ 原样保留
array              ⇒ [()=>item1.val, ()=>item2.val...]
普通对象            ⇒ 抛出警告(非法类型)

为什么需要这种转换?

  1. 统一依赖跟踪:无论数据来源如何,Vue 的响应式系统(effect)最终都需要通过 getter 函数来追踪依赖。
  2. 深层次监听reactive 对象需要自动开启 deep 以监听嵌套变化。
  3. 性能优化:对 Ref 的值访问(.value)需在 getter 中捕获才能触发响应式。
  4. 数组支持:保证多个源变更时能统一触发回调。

示例:实际等价转换

// 用户编写的代码
watch(
  [refObj, reactiveObj, () => computedVal.value],
  ([refNew, reactiveNew, computedNew]) => {...},
  { deep: true }
)

// Vue 实际运行的等价逻辑:
const getters = [
  () => refObj.value,
  () => reactiveObj,       // 自动触发 deep
  () => computedVal.value
];

effect(() => {
  const values = getters.map(get => get());
  // 跟踪所有依赖...
});

转换的实际意义

  1. 代码简化:允许开发者用最直观的方式传递侦听源。
  2. 性能深度控制:默认对 reactive 自动启用深度监听,其他类型按需处理。
  3. 泛用性支持:让数组/单源的逻辑能复用同一套响应式机制。

重要边界情况

  1. 深度监听优先级
    watch(reactiveObj, callback, { deep: false }) 
    // 即便手动设置 deep: false,Vue 依然会强制为 true
    
  2. 普通对象非法
    watch({ a: 1 }, callback) // ❌ 控制台警告
    

这些处理机制让 Vue 的 watch 在保持简洁 API 的同时,底层实现了响应式系统的统一管理。理解这些转换规则有助于写出更高效和可预测的侦听逻辑。

posted @   木燃不歇  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示