Vue.js响应式系统副作用函数触发时机
作者:技术拾光时间:2026-07-06 08:22:01浏览:574
effect 在创建时立即执行一次以完成依赖收集,后续仅在所读响应式数据变更且已被收集依赖时,由 scheduler 决定同步或异步执行;组件中 watchEffect 首次执行于挂载前,后续触发不受挂载状态影响。 许多开发人员误以为 Vue 的响应式副作用只要数据发生变化就会立刻执行,实际情况并非
effect 在创建时立即执行一次以完成依赖收集,后续仅在所读响应式数据变更且已被收集依赖时,由 scheduler 决定同步或异步执行;组件中 watchEffect 首次执行于挂载前,后续触发不受挂载状态影响。

许多开发人员误以为 Vue 的响应式副作用只要数据发生变化就会立刻执行,实际情况并非如此。触发时机由三个环节共同决定:依赖收集、数据变更通知以及调度器(scheduler)的执行策略。核心逻辑是:effect 只在其实际读取过的响应式属性发生变化时才会被通知,随后由调度器决定执行方式——默认同步执行,但更常见的情况是异步批量处理。理解这一机制,才是真正掌握 Vue 响应式调度原理的关键。
effect 创建时立即执行一次
调用 effect(fn) 时,Vue 会先运行一次传入的函数(即 run())。这不是多余操作,而是为了完成首次求值并启动依赖收集——相当于在初始阶段就将所有“监听触点”注册完毕。
- 例如:effect(() => console.log(state.count)) 会在定义那一刻立即打印当前 count 值
- 此时 state.count 被读取,Proxy 的 get 拦截器调用 track,将当前 effect 记录到 count 对应的依赖池中
也就是说,首次执行是绑定依赖的必要步骤,而非可选的初始化动作。
数据变更后触发重新执行
当 effect 内部读取过的响应式属性被修改(如 state.count = 5),set 拦截器会立即调用 trigger。trigger 会查找该属性对应的依赖池,从中取出所有相关的 effect 并安排执行。
- 触发的前提是:该属性确实被某个 effect 在其 fn 中访问过(即已完成依赖收集)
- 如果 effect 从未读取某个响应式字段,即使该字段被修改多次,也不会触发该 effect——这就是“精准触发”
- 需要注意的是:trigger 并不直接运行 effect,而是将执行权交给调度逻辑,由调度器控制如何执行以及何时执行
执行时机由 scheduler 控制
默认情况下,未配置 scheduler 的 effect 会同步执行——数据改变后立即运行。但 Vue 内部绝大多数场景(如组件更新)都使用了自定义 scheduler,从而实现异步批量更新。
- 例如 runtime-core 中的 scheduler 会将多个 effect 推入 Promise 微任务队列,在下一个 tick 统一执行,避免重复渲染
- watchEffect 或 computed 的刷新时机(如 flush: 'post')也依赖 scheduler 来实现 DOM 更新后的延迟执行
- 如果开发者手动传入 scheduler,那么 effect.run() 会被替换为 scheduler() 调用,从而完全掌控执行节奏——延迟、去重、按条件触发均可自定义
组件生命周期中的实际表现
在 setup 中使用 watchEffect 或 computed 时,触发时机还会受到组件挂载状态的影响。
- watchEffect 的首次执行发生在组件实例创建完成、DOM 挂载之前(大致对应 onBeforeMount 阶段)
- 后续触发仅响应其内部读取的响应式数据变化,与组件是否已 mounted 无关
- 如果 effect 依赖的 ref 或 reactive 尚未初始化(例如异步加载中),首次执行可能读到 undefined,此时需要添加空值判断,避免意外
