
TanStack Query notifyManager 源码级解析回调调度、批量更新与自定义通知机制【免费下载链接】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/querynotifyManager是 TanStack Query 框架无关核心query-core中负责调度与批量执行回调的轻量级管理器它决定了状态更新何时、以何种方式通知到各个 Observer 与订阅者。本文基于 docs/reference/notifyManager.md 展开并结合 notifyManager.ts 源码、测试用例及各框架适配层完整讲解batch、batchCalls、schedule、setNotifyFunction、setBatchNotifyFunction、setScheduler六个核心 API 的原理与实战用法。读完你将掌握为什么 QueryClient 的批量操作能合并渲染、React 测试中如何用act包裹通知、Solid Query 如何复用 Solid 的batch以及如何自定义调度时机微任务 / 动画帧 / 延时。一、notifyManager 是什么调度与批量的总开关TanStack Query 的核心是一个发布-订阅模型Query/Mutation状态变化后需要通知QueryObserver等订阅者进而触发框架层的重新渲染。如果每次状态微调都同步触发一次通知很容易造成重复渲染和性能浪费。notifyManager就是为了解决这个问题而存在的单例工具它统一负责三件事调度schedule决定回调在哪个时机执行默认通过setTimeout(callback, 0)延迟到下一个事件循环批量batch把同一事务内产生的多次更新合并成一次通知可定制setter把通知函数批量函数调度器全部抽象成可替换的接口让 React、Solid、Preact、Angular、Vue 等不同框架都能接入自己平台的批处理机制。在仓库中notifyManager由 packages/query-core/src/notifyManager.ts 定义并通过createNotifyManager()创建后以单例形式导出整个 query-core 及其上层框架包共享同一个实例// SINGLETON export const notifyManager createNotifyManager()文档中说明它暴露以下方法本文逐一展开方法作用batch(callback)批量执行callback 内所有 schedule 的更新在事务结束后统一 flushbatchCalls(callback)高阶函数包装后的函数所有调用都会被调度到下一个 batchschedule(callback)调度一个函数在下一个 batch 中运行setNotifyFunction(fn)覆盖执行回调的函数默认直接调用setBatchNotifyFunction(fn)设置批量更新的执行函数如 Solid 的batchsetScheduler(fn)配置何时运行下一个 batch的调度器默认setTimeout(cb, 0)二、batch把多次更新合并为一次通知function batchT(callback: () T): Tbatch用于把传入 callback 内部触发的所有更新调度合并起来在 callback 执行完毕后再统一通知。文档明确指出它主要被内部用来优化queryClient的更新。真实调用证据在 queryClient.ts 中多个批量 API 都用notifyManager.batch包裹。例如setQueriesData会同时更新多个 query 的数据整个遍历过程被包在一个batch内避免每个 query 各自触发一次订阅者通知// packages/query-core/src/queryClient.ts#L225-L232 return notifyManager.batch(() this.#queryCache .findAll(filters) .map(({ queryKey }) [ queryKey, this.setQueryDataTQueryFnData(queryKey, updater, options), ]), )类似的用法还遍布于queryClient.ts 的removeQueries第 254 行、resetQueries第 267 行、cancelQueries第 289 行queryCache.ts 的find/remove/clear等内部通知mutation.ts 的dispatch在一次 mutation 状态变更时同时通知所有 observers 和 mutationCache第 397–406 行// packages/query-core/src/mutation.ts#L397-L406 notifyManager.batch(() { this.#observers.forEach((observer) { observer.onMutationUpdate(action) }) this.#mutationCache.notify({ mutation: this, type: updated, action, }) })batch支持嵌套内部再调用batch并且即使 callback 抛出异常也会保证 flush 发生——这一点有测试用例专门覆盖见下文测试验证一节确保事务不会因异常而泄漏未完成的通知。三、batchCalls把任意回调变成批量调度版type BatchCallsCallbackT extends Arrayunknown (...args: T) void function batchCallsT extends Arrayunknown( callback: BatchCallsCallbackT, ): BatchCallsCallbackTbatchCalls是一个高阶函数传入一个回调返回一个包装后的新函数。对包装函数的所有调用都不会立即执行原回调而是通过schedule把它排队到下一个 batch 统一执行——效果等同于把多次事件触发合并成一次通知。这是框架适配层最常用的 API 之一。例如在 Preact Query 中Observer 的订阅回调被包装后交给 store// packages/preact-query/src/useBaseQuery.ts#L99 ? observer.subscribe(notifyManager.batchCalls(onStoreChange))在 Angular Queryexperimental中injectQueries等多个组合式 API 也用它包裹状态更新回调把来自 query-core 的高频通知合批后再驱动 Angular 变更检测// packages/angular-query-experimental/src/inject-queries.ts#L303 notifyManager.batchCalls((state) { ... })类型层面batchCalls保持参数签名不变测试文件 notifyManager.test.tsx 第 82–98 行专门用expectTypeOf校验包装前后参数类型完全一致传入(a: string, b: number) string包装函数仍接受相同签名错误参数会被 TypeScript 报错。四、schedule调度回调到下一个 batchfunction schedule(callback: () void): voidschedule把回调安排到下一个 batch运行。默认情况下这个 batch 通过setTimeout执行但可以通过setScheduler完全替换详见第六节。从源码看schedule的逻辑是// packages/query-core/src/notifyManager.ts const schedule (callback: NotifyCallback): void { if (transactions) { // 处于 batch 事务中入队等待 flush queue.push(callback) } else { // 不在事务中直接交给调度器 scheduleFn(() { notifyFn(callback) }) } }关键在于transactions计数器当处于batch事务内时回调只进队列不执行事务结束后由flush统一派发。因此在batch之外调用schedule就等于尽快异步执行。Preact Query 的useMutationState就单独用它来安排 store 更新// packages/preact-query/src/useMutationState.ts#L186 notifyManager.schedule(onStoreChange)五、setNotifyFunction接管通知执行典型场景是 React.actnotifyManager.setNotifyFunction(fn)setNotifyFunction用来覆盖真正执行回调的那个函数。默认的notifyFunction只是直接调用回调源码第 20–22 行let notifyFn: NotifyFunction (callback) { callback() }文档给出了最经典的实战场景在 React 测试中把通知用act包裹让 React 在受控环境下处理状态更新避免测试中的 act 警告import { notifyManager } from tanstack/react-query import { act } from react-dom/test-utils notifyManager.setNotifyFunction(act)这一模式在仓库的测试基建中被大量复用。例如 packages/preact-query/test-setup.ts 第 12 行、packages/react-query/test-setup.ts、packages/query-broadcast-client-experimental/test-setup.ts 等均通过setNotifyFunction把测试环境中的通知收敛进框架的批处理上下文。源码注释也明确写着这一用途notifyManager.ts 第 79–81 行/** * Use this method to set a custom notify function. * This can be used to for example wrap notifications with React.act while running tests. */ setNotifyFunction: (fn: NotifyFunction) { notifyFn fn },六、setBatchNotifyFunction接入框架自有批处理能力notifyManager.setBatchNotifyFunction(fn)setBatchNotifyFunction设置用于批量更新的函数。默认的batchNotifyFn同样只是直接调用源码第 23–25 行let batchNotifyFn: BatchNotifyFunction (callback: () void) { callback() }文档强调如果你的框架支持自定义批处理函数可以把它告诉 TanStack Query。最典型的例子是 Solid Query——Solid 自带batch于是把notifyManager的批量执行直接替换为 Solid 的batch从而让通知进入 Solid 的响应式批处理周期import { notifyManager } from tanstack/query-core import { batch } from solid-js notifyManager.setBatchNotifyFunction(batch)对应源码注释notifyManager.ts 第 86–88 行也说明默认情况下 React Query 会使用 ReactDOM / React Native 提供的 batch 函数。也就是说setBatchNotifyFunction是 TanStack Query 实现框架无关的关键机制之一——核心层只定义批量这一抽象具体语义由各框架注入。在 Solid Query 的源码中也能看到同样的思路useQueries内部用 Solid 的batch包裹状态写入例如 packages/solid-query/src/useQueries.ts 第 262 行与第 275 行以及useQuery.test.tsx中 should batch re-renders 的测试用例验证了批量渲染行为。七、setScheduler自定义下一个 batch何时运行notifyManager.setScheduler(fn)setScheduler配置的是一个调度器它接收一个回调负责决定下一个 batch 什么时候运行。默认行为等价于setTimeout(callback, 0)。文档给出了三种典型的自定义方案import { notifyManager } from tanstack/react-query // 在下一个微任务中调度 batch比 setTimeout 更早执行 notifyManager.setScheduler(queueMicrotask) // 在下一帧渲染之前调度 batch适合与绘制节奏对齐 notifyManager.setScheduler(requestAnimationFrame) // 延迟一段时间后再执行 batch notifyManager.setScheduler((cb) setTimeout(cb, 10))从源码看默认调度器并不直接写setTimeout而是引用了 timeoutManager.ts 导出的systemSetTimeoutZero第 136–138 行其实现就是// packages/query-core/src/timeoutManager.ts export function systemSetTimeoutZero(callback: TimeoutCallback): void { setTimeout(callback, 0) }这种封装是为了让query-core统一审计系统setTimeout的使用避免在不可信环境下直接依赖全局定时器。八、源码内部机制队列、事务与 flush理解notifyManager只需抓住三个内部状态notifyManager.ts 第 18–26 行let queue: ArrayNotifyCallback [] // 待通知回调队列 let transactions 0 // 事务计数batch 嵌套深度 let notifyFn: NotifyFunction (callback) { callback() } let batchNotifyFn: BatchNotifyFunction (callback) { callback() } let scheduleFn defaultScheduler // 默认 setTimeout(cb, 0)完整执行流程调用batch(callback)→transactions执行 callbackcallback 内部的schedule由于transactions 0只把回调推入 queuecallback 执行结束无论成功或抛错finally保证→transactions--当transactions归零时调用flush()flush取出整个队列并重置再通过调度器安排一次批量派发scheduleFn决定何时执行执行时先调batchNotifyFn框架可自定义如 Solid 的batch批量内部对每个回调调用notifyFn框架可自定义如 React 测试的act。// packages/query-core/src/notifyManager.ts const flush (): void { const originalQueue queue queue [] if (originalQueue.length) { scheduleFn(() { batchNotifyFn(() { originalQueue.forEach((callback) { notifyFn(callback) }) }) }) } }batchCalls的底层实现就是包装后的每次调用都走schedule与batch配合即实现多次调用、一次通知// packages/query-core/src/notifyManager.ts batchCalls: T extends Arrayunknown(callback) { return (...args) { schedule(() { callback(...args) }) } },另外createNotifyManager每次调用都会创建独立的闭包状态这为测试提供了便利——测试中可以createNotifyManager()得到互不干扰的实例见下文。九、测试验证行为有据可查packages/query-core/src/tests/notifyManager.test.tsx 使用 Vitest 对上述行为做了完整覆盖可以作为理解每个 API 语义的活的文档默认 notifyFn第 23–29 行schedule(callback)后推进假定时器回调被调用默认 batchNotifyFn 与嵌套 batch第 31–46 行batch(batch(batch(schedule)))三层嵌套只各执行一次证明事务计数器正确归零后才 flush自定义 scheduler第 48–62 行setScheduler(queueMicrotask)后调度器被调用且 notify 最终执行异常安全第 64–80 行batch内schedule后抛出错误捕获后推进定时器通知仍然执行——验证finally中的 flush 逻辑batchCalls 类型保持第 82–98 行包装函数参数签名与原始回调完全一致自定义 batchNotifyFunction第 100–118 行同一 batch 内的多个schedule回调经批量函数统一执行batchCalls 行为第 120–132 行包装函数以原始参数调用后回调在下一批被执行。这些测试同时证明了notifyManager的可测试性设计createNotifyManager()可创建隔离实例配合vi.useFakeTimers()即可精准控制调度时机。十、总结与使用建议notifyManager是 TanStack Query 全部状态通知的调度中枢其设计可以概括为三层抽象层级默认实现可替换 API典型替换何时执行调度setTimeout(cb, 0)setSchedulerqueueMicrotask、requestAnimationFrame如何批量直接调用setBatchNotifyFunctionSolid 的batch、ReactDOM 的批处理如何通知直接调用setNotifyFunctionReact 测试中的act对普通使用者而言日常开发通常不需要直接操作notifyManager——batch的优化在queryClient.setQueriesData、removeQueries、mutation 派发等内部路径已经自动生效。但当你遇到以下场景时它就变得不可或缺写框架适配层用batchCalls包装订阅回调把高频通知合批后驱动自己的渲染管线写测试用setNotifyFunction(act)消除 React 的 act 警告对接自有框架 / 调度策略用setBatchNotifyFunction接入平台的批处理能力用setScheduler控制通知时机如与动画帧对齐。深入阅读建议notifyManager 源码、notifyManager 测试、queryClient 中的批量应用、solid-query 的 batch 集成。【免费下载链接】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),仅供参考