vue3 高级响应式函数 watch watchEffect computed
vue3 高级响应式函数 watch watchEffect computed
qf_luckVue3 高级响应式函数
Vue3 的响应式系统不仅提供了 reactive/ref 等基础 API,还通过 computed、watch、watchEffect 等高级函数实现了对响应式数据的精细化处理。这些函数基于响应式核心机制(依赖追踪与副作用调度),但各自聚焦不同场景,掌握它们的原理与差异是写出高效 Vue 代码的关键。
一、computed:带缓存的派生状态
computed 用于创建依赖响应式数据的派生状态,其核心特性是缓存机制——只有当依赖的响应式数据变化时,才会重新计算,否则直接返回缓存值。
1. 原理:基于副作用的缓存调度
computed 的底层依赖 Vue3 的 Effect 副作用系统,其工作流程可简化为:
- 初始化:创建一个
ComputedRefImpl实例,内部包含一个effect(副作用函数),该函数执行用户传入的getter逻辑。 - 依赖收集:首次访问
computed的value时,触发effect.run(),执行getter并收集getter中使用的响应式数据作为依赖。 - 缓存与更新:当依赖变化时,
effect会被标记为”脏”(dirty: true),但不会立即重新计算;只有再次访问value时,才会重新执行getter并更新缓存。
核心伪代码:
1 | class ComputedRefImpl { |
2. 参数与用法
computed 有两种使用形式,对应不同参数:
(1)只读 computed
参数为一个getter函数,返回一个只读的 ComputedRef 对象(需通过 .value 访问)。
1 | import { ref, computed } from 'vue'; |
(2)可写 computed
参数为一个包含 get 和 set 的配置对象,支持修改计算属性的值(会触发 set 逻辑)。
1 | const count = ref(1); |
3. 应用场景
- 派生数据处理:将多个响应式数据组合成新数据(如全名、总价、状态标识等)。
- 数据格式化:对原始数据进行格式化(如日期格式化、金额千分位处理)。
- 缓存高频计算:避免在模板或函数中重复执行耗时计算(如大数据过滤、复杂公式)。
反例:不要在 computed 中执行副作用(如修改DOM、发送请求),这违背其”派生状态”的设计初衷,应使用 watch 或 watchEffect。
二、watch:精确监听指定源的变化
watch 用于显式监听一个或多个响应式源,当源变化时执行自定义副作用(如异步请求、DOM操作等)。它的核心特点是精确控制监听对象,并能获取变化前后的值。
1. 原理:基于源的依赖追踪
watch 的底层同样依赖 Effect 系统,但与 computed 不同,它需要先明确”监听什么”,再定义”变化后做什么”。工作流程:
- 解析源:将用户传入的”源”(可以是
ref、reactive属性、getter函数等)标准化为一个可追踪的 getter 函数。 - 创建副作用:基于源的 getter 创建
effect,并在effect的调度器(scheduler)中执行用户传入的回调函数。 - 监听与触发:当源依赖的响应式数据变化时,
effect被触发,调度器会获取新旧值并执行回调。
核心伪代码:
1 | function watch(source, cb, options) { |
2. 参数详解
watch 的完整参数格式:watch(source, callback, options?)
(1)source:监听源
可以是以下类型:
- ref(包括
ComputedRef):直接监听其.value变化。 - reactive对象:监听对象内部所有属性(自动深层监听)。
- getter函数:监听函数返回值的变化(支持精确监听对象的某个属性)。
- 源数组:同时监听多个源,任意一个变化即触发回调。
(2)callback:变化回调
格式:(newValue, oldValue, onCleanup) => void
newValue:源的新值oldValue:源的旧值onCleanup:清理函数,用于注册副作用的清理逻辑(如取消请求、清除定时器)。
(3)options:配置选项
immediate: boolean:是否立即执行一次回调(默认false)。deep: boolean:是否深层监听(当源是对象且未用 getter 时生效,默认false,但监听reactive对象时自动深层)。flush: 'pre' | 'post' | 'sync':回调执行时机(默认'pre',即DOM更新前;'post'为DOM更新后;'sync'为同步执行)。onTrack/onTrigger:调试用,分别在依赖收集和触发更新时执行。
3. 示例:不同源的监听方式
(1)监听 ref
1 | const count = ref(0); |
(2)监听 reactive 对象的属性(需用 getter)
1 | const user = reactive({ name: '张三', age: 20 }); |
(3)监听多个源(数组形式)
1 | const a = ref(1); |
(4)深层监听与清理函数
1 | const obj = reactive({ nested: { count: 0 } }); |
4. 应用场景
- 数据变化触发异步操作:如用户ID变化后重新请求用户详情。
- 状态同步:如表单输入变化后同步更新本地存储。
- 复杂副作用处理:需要精确控制执行时机(如DOM更新后操作)或清理逻辑(如防抖、节流)的场景。
注意:监听 reactive 对象时,watch 会自动深层监听,但性能开销较大;建议用 getter 函数精确监听所需属性(如 () => obj.prop)。
三、watchEffect:自动追踪的副作用
watchEffect 用于自动追踪函数内部的响应式依赖,当依赖变化时重新执行函数。它的核心特点是无需指定监听源,专注于”副作用逻辑”,更简洁但灵活性稍低。
1. 原理:隐式依赖收集
watchEffect 是 watch 的简化版,它省略了”指定源”的步骤,直接执行副作用函数并自动收集其中的响应式依赖。工作流程:
- 立即执行:创建
effect并立即执行副作用函数。 - 自动收集依赖:执行过程中读取的响应式数据(
ref.value、reactive属性等)会被自动标记为依赖。 - 依赖变化触发:当依赖变化时,自动重新执行副作用函数。
核心伪代码:
1 | function watchEffect(effectFn, options) { |
2. 参数与用法
watchEffect 的参数格式:watchEffect(effectFn, options?)
(1)effectFn:副作用函数
格式:(onCleanup) => void
- 函数内部可直接使用响应式数据,这些数据会被自动追踪。
onCleanup:清理函数,用于注册副作用的清理逻辑(与watch的onCleanup一致)。
(2)options:配置选项
flush: 'pre' | 'post' | 'sync':执行时机(默认'pre',同watch)。onTrack/onTrigger:调试用,同watch。
3. 示例:自动追踪依赖
1 | import { ref, watchEffect } from 'vue'; |
4. 与 watch 的核心区别
| 特性 | watch |
watchEffect |
|---|---|---|
| 监听源 | 需显式指定(精确控制) | 自动追踪函数内的依赖(隐式) |
| 执行时机 | 默认不立即执行(可通过 immediate 开启) |
立即执行一次(首次运行收集依赖) |
| 新旧值 | 可获取 newValue 和 oldValue |
无法直接获取,需手动缓存旧值 |
| 适用场景 | 需精确监听、需要新旧值、控制执行时机 | 简单副作用、自动追踪多依赖 |
5. 应用场景
- 响应式数据驱动的副作用:如搜索框输入变化后自动请求接口、表单验证。
- 资源自动释放:如根据响应式数据动态创建/销毁定时器、事件监听。
- 简单的状态同步:如响应式数据变化后同步更新DOM样式、日志输出。
四、总结:如何选择?
- 需要派生状态且有缓存 →
computed(如fullName = firstName + lastName)。 - 需要精确监听指定源、需要新旧值、控制执行时机 →
watch(如监听用户ID变化请求数据)。 - 需要自动追踪多依赖、执行简单副作用 →
watchEffect(如搜索输入变化后请求接口)。
这三个函数共同构建了Vue3响应式系统的上层能力,理解它们的原理与差异,能让你在处理响应式数据时更高效、更贴合场景。

