以下是 Vue 3 watch
第一个参数的一切形式和最终转换形态的完整解析:
核心结论
在 Vue 3 中,watch
第一个参数的所有形式最终都会被统一转换成:
- 一个 getter 函数(当侦听单个来源时)
- 由 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.27
的 doWatch
函数为例(关键片段):
处理入口
文件位置: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...]
普通对象 ⇒ 抛出警告(非法类型)
为什么需要这种转换?
- 统一依赖跟踪:无论数据来源如何,Vue 的响应式系统(
effect
)最终都需要通过 getter 函数来追踪依赖。 - 深层次监听:
reactive
对象需要自动开启deep
以监听嵌套变化。 - 性能优化:对 Ref 的值访问(
.value
)需在 getter 中捕获才能触发响应式。 - 数组支持:保证多个源变更时能统一触发回调。
示例:实际等价转换
// 用户编写的代码
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());// 跟踪所有依赖...
});
转换的实际意义
- 代码简化:允许开发者用最直观的方式传递侦听源。
- 性能深度控制:默认对 reactive 自动启用深度监听,其他类型按需处理。
- 泛用性支持:让数组/单源的逻辑能复用同一套响应式机制。
重要边界情况
- 深度监听优先级:
watch(reactiveObj, callback, { deep: false }) // 即便手动设置 deep: false,Vue 依然会强制为 true
- 普通对象非法:
watch({ a: 1 }, callback) // ❌ 控制台警告
这些处理机制让 Vue 的 watch
在保持简洁 API 的同时,底层实现了响应式系统的统一管理。理解这些转换规则有助于写出更高效和可预测的侦听逻辑。