watchEffect 本身不会主动感知浏览器标签页当前是否处于可见状态,因此需要先将 document.visibilityState 封装为 ref 类型的响应式变量,再借助 visibilitychange 事件持续同步它的变化;这样一来,watchEffect 就能随着 isVisible.value 的更新自动响应,从而实现页面前台恢复、后台暂停等逻辑的自动切换。

简单来说,watchEffect 并不会直接判断浏览器标签页是否可见,它只会对 Vue 响应式数据的变化作出反应。想让 watchEffect 跟随页面可见性切换实现“同步”,关键并不是让它直接监听 document.hidden,而是先把标签页可见性处理成一个响应式状态,再交给 watchEffect 进行追踪和侦听。
把 visibilityState 封装成 ref
document.visibilityState 是浏览器提供的只读属性,无法直接被 watchEffect 侦听。正确做法是用 ref 手动维护它的当前状态,并在 visibilitychange 事件触发时同步更新:
- 定义一个 ref,例如 const isVisible = ref(document.visibilityState === 'visible')
- 在 onMounted 中注册 window.addEventListener('visibilitychange', () => { isVisible.value = document.visibilityState === 'visible' })
- 确保事件监听器在组件卸载时及时移除(可通过 onUnmounted 清理)
在 watchEffect 中使用该 ref
当 isVisible 成为真正的响应式数据后,watchEffect 就可以立即感知它的变化:
- watchEffect(() => { if (isVisible.value) { console.log('标签页切回前台,可恢复轮询或数据同步') } else { console.log('标签页已进入后台,暂停高消耗操作') } })
- 它会在首次执行时先运行一次,之后每当 isVisible.value 发生变化(例如切换到其他 tab 或窗口最小化),回调都会再次触发
- 不需要手动调用,也无需依赖定时轮询,整个过程完全由浏览器原生事件驱动
配合实际业务逻辑做开关控制
仅仅打印日志没有太大价值,核心在于利用这个信号来控制副作用逻辑的开启与暂停:
- 轮询类任务:标签页不可见时 clearInterval,可见后再重新启动 setInterval
- 视频同步场景:页面隐藏时暂停 currentTime 上报,恢复可见后再校准一次播放进度
- 版本检测:避免后台 tab 持续频繁请求 /version.json,仅在 visible 状态下执行 fetch
- 注意:不要在 watchEffect 内部直接修改 isVisible.value,否则容易造成响应式循环
避免常见陷阱
visibilitychange 事件虽然实用,但也存在一定局限,开发时需要额外留意:
- 页面首次加载时,visibilityState 可能是 'prerender' 或 'unloaded',建议初始化时结合 document.hasFocus() 判断,或直接以 'visible' 作为兜底值
- 在移动端切到后台、iOS Safari 切换 app 等场景下也可能触发,但部分旧版本浏览器对 visibilitychange 支持不完整,可结合 try/catch 做兼容处理
- watchEffect 默认使用 flush: 'pre',也就是在 DOM 更新前执行,适合做状态预处理;如果需要操作更新后的 DOM,可以加上 { flush: 'post' }
