news 2026/9/10 17:08:39

TanStack Query notifyManager 源码级解析:回调调度、批量更新与自定义通知机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TanStack Query notifyManager 源码级解析:回调调度、批量更新与自定义通知机制

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/query

notifyManager是 TanStack Query 框架无关核心(query-core)中负责调度与批量执行回调的轻量级管理器,它决定了状态更新何时、以何种方式通知到各个 Observer 与订阅者。本文基于 docs/reference/notifyManager.md 展开,并结合 notifyManager.ts 源码、测试用例及各框架适配层,完整讲解batchbatchCallsschedulesetNotifyFunctionsetBatchNotifyFunctionsetScheduler六个核心 API 的原理与实战用法。读完你将掌握:为什么 QueryClient 的批量操作能合并渲染、React 测试中如何用act包裹通知、Solid Query 如何复用 Solid 的batch,以及如何自定义调度时机(微任务 / 动画帧 / 延时)。

一、notifyManager 是什么:调度与批量的"总开关"

TanStack Query 的核心是一个发布-订阅模型:Query/Mutation状态变化后需要通知QueryObserver等订阅者,进而触发框架层的重新渲染。如果每次状态微调都同步触发一次通知,很容易造成重复渲染和性能浪费

notifyManager就是为了解决这个问题而存在的单例工具,它统一负责三件事:

  1. 调度(schedule):决定回调在哪个时机执行(默认通过setTimeout(callback, 0)延迟到下一个事件循环);
  2. 批量(batch):把同一事务内产生的多次更新合并成一次通知;
  3. 可定制(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 的更新在事务结束后统一 flush
batchCalls(callback)高阶函数:包装后的函数所有调用都会被调度到下一个 batch
schedule(callback)调度一个函数在下一个 batch 中运行
setNotifyFunction(fn)覆盖"执行回调"的函数(默认直接调用)
setBatchNotifyFunction(fn)设置批量更新的执行函数(如 Solid 的batch
setScheduler(fn)配置"何时运行下一个 batch"的调度器(默认setTimeout(cb, 0)

二、batch:把多次更新合并为一次通知

function batch<T>(callback: () => T): T

batch用于把传入 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.setQueryData<TQueryFnData>(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 BatchCallsCallback<T extends Array<unknown>> = (...args: T) => void function batchCalls<T extends Array<unknown>>( callback: BatchCallsCallback<T>, ): BatchCallsCallback<T>

batchCalls是一个高阶函数:传入一个回调,返回一个包装后的新函数。对包装函数的所有调用都不会立即执行原回调,而是通过schedule把它排队到下一个 batch 统一执行——效果等同于"把多次事件触发合并成一次通知"。

这是框架适配层最常用的 API 之一。例如在 Preact Query 中,Observer 的订阅回调被包装后交给 store:

// packages/preact-query/src/useBaseQuery.ts#L99 ? observer.subscribe(notifyManager.batchCalls(onStoreChange))

在 Angular Query(experimental)中,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:调度回调到下一个 batch

function schedule(callback: () => void): void

schedule把回调安排到"下一个 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.act

notifyManager.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: Array<NotifyCallback> = [] // 待通知回调队列 let transactions = 0 // 事务计数(batch 嵌套深度) let notifyFn: NotifyFunction = (callback) => { callback() } let batchNotifyFn: BatchNotifyFunction = (callback) => { callback() } let scheduleFn = defaultScheduler // 默认 setTimeout(cb, 0)

完整执行流程:

  1. 调用batch(callback)transactions++,执行 callback;
  2. callback 内部的schedule由于transactions > 0,只把回调推入 queue
  3. callback 执行结束(无论成功或抛错,finally保证)→transactions--;当transactions归零时调用flush()
  4. 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 Array<unknown>>(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 行):batchschedule后抛出错误,捕获后推进定时器,通知仍然执行——验证finally中的 flush 逻辑;
  • batchCalls 类型保持(第 82–98 行):包装函数参数签名与原始回调完全一致;
  • 自定义 batchNotifyFunction(第 100–118 行):同一 batch 内的多个schedule回调经批量函数统一执行;
  • batchCalls 行为(第 120–132 行):包装函数以原始参数调用后,回调在下一批被执行。

这些测试同时证明了notifyManager可测试性设计createNotifyManager()可创建隔离实例,配合vi.useFakeTimers()即可精准控制调度时机。

十、总结与使用建议

notifyManager是 TanStack Query 全部状态通知的"调度中枢",其设计可以概括为三层抽象

层级默认实现可替换 API典型替换
何时执行(调度)setTimeout(cb, 0)setSchedulerqueueMicrotaskrequestAnimationFrame
如何批量直接调用setBatchNotifyFunctionSolid 的batch、ReactDOM 的批处理
如何通知直接调用setNotifyFunctionReact 测试中的act

对普通使用者而言,日常开发通常不需要直接操作notifyManager——batch的优化在queryClient.setQueriesDataremoveQueries、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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 17:08:33

OpenSSL 中的 SLH-DSA(SLH-DSA)状态哈希签名实现设计详解

OpenSSL 中的 SLH-DSA&#xff08;SLH-DSA&#xff09;状态哈希签名实现设计详解 【免费下载链接】openssl General purpose TLS and crypto library 项目地址: https://gitcode.com/GitHub_Trending/ope/openssl 导读 SLH-DSA&#xff08;Stateless Hash-Based Digita…

作者头像 李华
网站建设 2026/9/10 17:07:15

水下垃圾检测数据集处理全流程:从zip解压到YOLOv8训练

简介&#xff1a;面向水下垃圾自动识别场景&#xff0c;这份数据集可用于训练和评估目标检测模型&#xff0c;适合从事水下环境监测、海洋AI应用的学生和工程师使用。压缩包大小为173.21MB&#xff0c;解压后包含7685个xml标注、7684个jpg图像和7684个txt文本&#xff0c;共两万…

作者头像 李华
网站建设 2026/9/10 17:06:54

深度强化学习实现实时系统任务动态优先级分配与PPO训练实践

简介&#xff1a;基于深度强化学习的实时系统任务优先级动态分配算法设计源码&#xff0c;面向实时系统、嵌入式及调度算法研究者与开发者。实时系统中任务的紧急性和重要性动态变化&#xff0c;传统静态优先级难以高效应对&#xff0c;该方案将深度学习特征提取与强化学习决策…

作者头像 李华
网站建设 2026/9/10 17:06:40

健康产业展会联动模式与无障碍设计实践

1. 展会联动模式解析&#xff1a;四展同台的战略价值广州福祉展采用的"四展联动"模式&#xff0c;本质上是通过产业协同效应实现资源整合的经典案例。这种模式在会展行业被称为"Co-located Exhibitions"&#xff0c;即多个相关主题展会同期同地举办。具体到…

作者头像 李华
网站建设 2026/9/10 17:04:41

SpringBoot构建高并发考研资讯平台架构实践

1. 项目概述&#xff1a;考研资讯平台的SpringBoot实现考研资讯平台是面向全国考研学子的一站式信息聚合系统&#xff0c;基于SpringBoot框架构建的后端服务能够高效处理每年数百万考生的实时查询需求。这个项目不同于普通的内容管理系统&#xff0c;它需要应对考研季爆发式的流…

作者头像 李华