TanStack Query FocusManager 完全指南掌控焦点状态与窗口重聚焦时的数据刷新【免费下载链接】query Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.项目地址: https://gitcode.com/GitHub_Trending/qu/query导读FocusManager是 TanStack Query 核心层query-core中负责统一管理窗口/页面焦点状态的单例管理器它决定了查询在用户切换到其他标签页再返回时是否自动重新获取数据。本文将以官方参考文档为主体结合仓库源码深入讲解FocusManager的四个核心方法setEventListener、subscribe、setFocused、isFocused剖析它如何接入浏览器的visibilitychange事件、如何与重试机制retryer、QueryClient挂载生命周期及refetchInterval定时刷新协同工作并给出覆盖 React、Vue、Svelte 等框架的实战用法与测试验证。读完本文你将能完全掌控 TanStack Query 的窗口焦点感知行为并能针对特定框架或业务场景自定义焦点事件源。一、FocusManager 是什么FocusManager在 TanStack Query 中负责管理焦点状态focus state。默认情况下TanStack Query 会在浏览器窗口重新获得焦点例如用户切走标签页后再切回来时自动重新获取refetch那些处于 stale 状态的查询——这一魔法行为的底层就是FocusManager。它的两大职责来自官方文档改变默认的事件监听器用于适配 React Native、Web 之外的运行环境或自定义事件源手动改变焦点状态用于在自动化测试、或需要人为控制刷新时机时直接注入状态。官方文档给出的可用方法共四个全部可直接通过框架包导出的单例调用setEventListenersubscribesetFocusedisFocused在仓库中FocusManager类位于 packages/query-core/src/focusManager.ts并通过export const focusManager new FocusManager()导出一个全局单例同时从 packages/query-core/src/index.ts 对外导出。各框架包如react-query、vue-query、svelte-query均从tanstack/query-core再导出该单例因此无论你使用哪个框架都可以这样导入import { focusManager } from tanstack/react-query // Vue / Svelte / Solid 等其他框架同理 // import { focusManager } from tanstack/vue-query // import { focusManager } from tanstack/svelte-query // import { focusManager } from tanstack/solid-query二、focusManager.setEventListener自定义焦点事件源2.1 官方用法setEventListener用于设置自定义的事件监听器。当默认的visibilitychange监听不适用于你的运行环境时例如 React Native、Electron、小程序容器可以用它替换掉默认实现import { focusManager } from tanstack/react-query focusManager.setEventListener((handleFocus) { // Listen to visibilitychange if (typeof window ! undefined window.addEventListener) { window.addEventListener(visibilitychange, handleFocus, false) } return () { // Be sure to unsubscribe if a new handler is set window.removeEventListener(visibilitychange, handleFocus) } })setEventListener接收一个setup函数TanStack Query 会把一个handleFocus回调传给它setup 函数负责把该回调挂到任意事件源上并返回一个清理函数用于在新监听器替换或订阅全部取消时解除绑定。2.2 源码级解析替换与清理机制从源码 packages/query-core/src/focusManager.ts 可以看到setEventListener的实现setEventListener(setup: SetupFn): void { this.#setup setup this.#cleanup?.() this.#cleanup setup((focused) { if (typeof focused boolean) { this.setFocused(focused) } else { this.onFocus() } }) }三个关键点先清理再替换每次调用setEventListener都会先执行上一次 setup 返回的#cleanup确保旧的visibilitychange监听被移除不会发生事件泄漏或重复触发回调参数可带值可空传入 setup 的handleFocus既可以接收boolean直接设置焦点状态也可以无参调用走默认的焦点判定逻辑onFocus()默认 setup在FocusManager构造函数中focusManager.ts内置了默认 setup——当typeof window ! undefined window.addEventListener时监听visibilitychange事件并返回对应的removeEventListener清理函数。值得注意的是源码注释明确说明addEventListener在 React Native 中不存在但window存在因此该守卫是必须的。测试 packages/query-core/src/tests/focusManager.test.tsx 中验证了这些行为should call previous remove handler when replacing an event listener连续两次setEventListener第一次的 remove 函数会被调用一次第二次的不会被调用——印证了替换即清理cleanup (removeEventListener) should not be called if window is not defined 与 …if window.addEventListener is not defined在window不存在或window.addEventListener不存在时取消订阅不会调用removeEventListener印证了默认 setup 的环境守卫逻辑。2.3 实战React Native 自定义事件源在 React Native 中标准的visibilitychange并不存在社区常用AppStateAPI 来感知前后台切换。你可以这样接入import { AppState } from react-native import { focusManager } from tanstack/react-query focusManager.setEventListener((handleFocus) { const subscription AppState.addEventListener(change, (status) { handleFocus(status active) }) return () subscription.remove() })这样当 App 从后台回到前台active时TanStack Query 就会像 Web 端切回标签页一样触发焦点恢复逻辑。三、focusManager.subscribe订阅焦点状态变化subscribe用于订阅可见性/焦点状态的变化并返回一个取消订阅的函数官方文档强调It returns an unsubscribe functionimport { focusManager } from tanstack/react-query const unsubscribe focusManager.subscribe((isVisible) { console.log(isVisible, isVisible) }) // 不再需要时取消订阅 unsubscribe()3.1 底层机制继承自 SubscribableFocusManager继承自 packages/query-core/src/subscribable.ts 中的Subscribable基类subscribe(listener)将监听器加入Set触发onSubscribe()返回一个删除监听器并触发onUnsubscribe()的取消函数FocusManager覆写了onSubscribe/onUnsubscribefocusManager.ts当第一个订阅者出现时才通过setEventListener(this.#setup)挂载事件监听当最后一个订阅者取消后执行#cleanup并置空实现按需挂载、零订阅零开销。测试 should call removeEventListener when last listener unsubscribesfocusManager.test.tsx验证了两个订阅者只注册一次visibilitychange第二个取消订阅时才真正移除监听。3.2 谁在订阅QueryClient 挂载时接入QueryClient.mount()packages/query-core/src/queryClient.ts正是通过subscribe与焦点状态联动的mount(): void { this.#mountCount if (this.#mountCount ! 1) return this.#unsubscribeFocus focusManager.subscribe(async (focused) { if (focused) { await this.resumePausedMutations() this.#queryCache.onFocus() } }) // ... }即窗口重新获得焦点时QueryClient会先恢复被暂停的 mutation再通知QueryCache对所有受影响的查询执行onFocus刷新queryCache.onFocus()会调用查询的onFocus()以重新获取数据。这也解释了为何默认行为是切回标签页自动刷新。当unmount()且挂载计数归零时对应订阅会被取消避免内存泄漏。四、focusManager.setFocused手动控制焦点状态setFocused用于手动设置焦点状态。传undefined时则回退到默认的焦点检查逻辑即基于document.visibilityState的判定import { focusManager } from tanstack/react-query // Set focused focusManager.setFocused(true) // Set unfocused focusManager.setFocused(false) // Fallback to the default focus check focusManager.setFocused(undefined)Optionsfocused: boolean | undefined4.1 源码行为变更才通知从源码 focusManager.ts 可以看到setFocused(focused?: boolean): void { const changed this.#focused ! focused if (changed) { this.#focused focused this.onFocus() } }只有状态实际发生变化时才会广播给所有监听者连续设置相同的值不会触发通知。测试 should call listeners when setFocused is calledfocusManager.test.tsx精确验证了这一点连续两次setFocused(true)只通知一次随后setFocused(undefined)会回退到默认判定测试环境中document被模拟为可见因此回调收到true。4.2 实战场景测试场景在 Vitest/Jest 中模拟窗口失焦/聚焦无需真实触发浏览器事件// 模拟失焦让重试与定时刷新暂停 focusManager.setFocused(false) // 恢复聚焦 focusManager.setFocused(true)后台预取场景某些场景下你希望在页面名义上失焦时仍不中断查询或反过来强制让查询感知到焦点均可通过此 API 精确控制。五、focusManager.isFocused读取当前焦点状态isFocused返回当前焦点状态booleanconst isFocused focusManager.isFocused()5.1 默认判定逻辑源码 focusManager.ts 的默认实现isFocused(): boolean { if (typeof this.#focused boolean) { return this.#focused } // document global can be unavailable in react native return globalThis.document?.visibilityState ! hidden }若此前通过setFocused(boolean)手动设置过则直接返回该值否则基于globalThis.document?.visibilityState ! hidden判定即只要页面不是hidden状态就算聚焦。源码注释特别说明document全局对象在 React Native 中可能不存在因此使用了可选链此时判定为true不会误判为失焦。测试 should return true for isFocused if document is undefinedfocusManager.test.tsx专门验证了删除globalThis.document后isFocused()返回true的边界行为。六、FocusManager 在数据获取链路中的关键作用FocusManager 并非孤立存在它在查询生命周期中承担着节流阀的角色直接影响数据获取的启停。6.1 重试暂停retryer.ts查询的获取与重试由 packages/query-core/src/retryer.ts 中的createRetryer驱动。其canContinue判定retryer.ts为const canContinue () focusManager.isFocused() (config.networkMode always || onlineManager.isOnline()) config.canRun()当查询失败进入重试等待时如果窗口失焦或设备离线run循环会调用pause()挂起重试源码注释明确写着 Pause if the document is not visible or when the device is offline一旦重新聚焦focusManager状态变化等待中的重试继续执行。这正是失焦时停止无谓请求、聚焦时无缝续传的核心机制。6.2 定时刷新守卫queryObserver.ts在QueryObserver的定时重取逻辑中packages/query-core/src/queryObserver.tsrefetchInterval的每次触发都会检查焦点this.#refetchIntervalId timeoutManager.setInterval(() { if ( this.options.refetchIntervalInBackground || focusManager.isFocused() ) { this.#executeFetch() } }, this.#currentRefetchInterval)即默认情况下窗口失焦时定时刷新被跳过只有显式设置refetchIntervalInBackground: true才在后台继续刷新。6.3 焦点恢复联动queryClient.tsqueryCache.ts如 3.2 节所述QueryClient.mount()订阅焦点变化后调用queryCache.onFocus()packages/query-core/src/queryCache.ts后者遍历所有查询触发各自的query.onFocus()完成回到页面即刷新 stale 数据的默认行为。七、与在线状态管理器的对比FocusManager与OnlineManager结构对称、职责互补FocusManager感知焦点OnlineManager感知网络。二者都以单例形式存在于query-core都提供setEventListener/subscribe/setFocused(isOnline)/isFocused(isOnline)这类同构 API并且在retryer.ts的canContinue中被并列判断见 6.1 节。如果你需要为离线优先的应用自定义网络状态判定可参考对应的 onlineManager.md 文档其使用模式与本文完全一致。八、要点总结方法作用典型场景setEventListener(setup)替换默认事件监听器默认监听visibilitychangeReact Native / Electron / 自定义事件源subscribe(listener)订阅焦点状态变化返回取消函数与QueryClient.mount()联动、自定义副作用setFocused(boolean \| undefined)手动设置焦点状态undefined回退默认判定测试模拟、强制控制刷新isFocused()读取当前焦点状态重试暂停/恢复、定时刷新守卫核心要点回顾默认事件源是浏览器的visibilitychangefocusManager.ts且带window.addEventListener存在性守卫兼容 React Native替换即清理setEventListener会先执行旧监听器的清理函数杜绝事件泄漏懒挂载只有出现第一个订阅者才挂载事件监听最后一个取消订阅后立即清理subscribable.ts变更才通知setFocused仅在状态实际变化时广播focusManager.test.tsx三处核心消费方retryer的重试暂停retryer.ts、QueryClient.mount()的焦点恢复刷新queryClient.ts、QueryObserver的定时刷新守卫queryObserver.ts。无论你是要适配非浏览器环境、优化移动端体验还是要编写稳定可靠的测试FocusManager都是你必须掌握的 TanStack Query 核心基础设施。【免费下载链接】query Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.项目地址: https://gitcode.com/GitHub_Trending/qu/query创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考