先记住这个答案
Vue 3.5 起,watch 和 watchEffect 的返回句柄提供 pause、resume 和 stop。pause 暂停后续响应式触发的处理,resume 恢复;暂停期间发生变化时,恢复后会按调度和源值判断处理当前状态,而不是逐条重放每次赋值。stop 则结束侦听并运行相应清理,不能用 resume 当成重新创建。暂停本身也不会取消已经发出的请求或冻结源值。
- 暂停不冻结响应式状态本身
- 恢复观察当前状态,不重放完整变更历史
- 停止与暂停的资源生命周期不同
暂停控制观察者而不是数据
在批量编辑场景中,暂停一个用于预览或同步的监听,源数据仍然可以继续修改,模板及其他监听也可以正常反应。暂停句柄不会自动暂停整个组件,更不会将多个字段的写入变成全局原子操作。
如果暂停前已有任务正在执行,暂停不会让普通函数突然返回,也不提供通用的 Promise 中断能力。需要暂时关闭外部连接或停止上传时,应调用对应资源接口,并明确恢复时如何重建。
恢复后检查最新结果
下例在监听稳定后暂停,随后连续修改源。恢复并等待刷新后,观察者从原先的零看到最新的二,中间的一不会被作为独立历史操作补发,这符合状态观察而非事件队列的用途。
使用默认 watch 时还存在最终值比较;若暂停期间先改动再改回原值,恢复不一定需要执行回调。业务若不能遗漏任何一次动作,应记录事件本身,而不是依赖暂停监听保存所有过渡状态。
import { ref, watch, nextTick } from 'vue'
const count = ref(0)
const seen: string[] = []
const handle = watch(count, (value, oldValue) => {
seen.push(oldValue + '->' + value)
})
handle.pause()
count.value = 1
count.value = 2
await nextTick()
const whilePaused = seen.slice()
handle.resume()
await nextTick()
handle.stop()
console.log(whilePaused, seen)暂停期间记录为空,恢复后记录零到二的一次变化。示例特意在修改之前暂停,没有将 pause 描述成撤回任意已排队任务或打断正在执行回调的机制。
暂停期间的资源需要另外安排
暂停不会因自己发生而运行上一轮清理,已有订阅或连接可能继续存在。若需求是面板不可见时停止轮询,可以显式停止轮询资源,在重新可见时重新创建,而不是只暂停用于配置轮询的监听。
停止后要再次观察,应创建新监听并明确初次执行策略。保存一个已经停止的句柄再调用 resume 不会重新建立原来的活动效果;接口调用方式相似,不代表它们具有相同的生命周期语义。
容易答错的地方
- 把 pause 当成轻量卸载
- 暂停保留观察关系,并不释放所有外部资源。页面长期隐藏时,仍需检查定时器、连接和未完成请求是否占用资源,按照业务生命周期清理,而不是仅依赖一个暂停方法。
- 恢复后期待逐次补发所有值
- 状态监听主要关心恢复时的有效状态,中间值可能被合并或比较后忽略。需要逐条提交、计费或统计的动作应进入显式队列,不能把暂停恢复接口当成可靠消息缓冲。
面试官还会怎么问?
暂停后修改状态,模板会停止更新吗?
不会因此整体停止。句柄管理的是这一份侦听器,组件渲染和其他效果有各自的依赖与调度。若希望界面显示也冻结,需要单独设计展示快照,不能从暂停一个监听推导出全局冻结。
pause 会立即执行 onCleanup 吗?
暂停本身不等于本轮副作用失效清理。下一次真正重跑前或停止时才会走相应清理路径。需要暂停期间释放资源时,应在业务暂停动作里明确执行释放,并安排恢复过程。
暂停期间没有变化,resume 会强制重跑吗?
不能把 resume 当作强制刷新按钮。它恢复观察能力,是否执行取决于是否存在待处理变化及具体监听的判断。如果业务需要主动刷新,建议提供显式刷新函数,而不是制造无意义的状态变化。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。