关于vue3 watch 第一个参数源码内部的处理方式
以下是 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 的同时,底层实现了响应式系统的统一管理。理解这些转换规则有助于写出更高效和可预测的侦听逻辑。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)