news 2026/9/9 20:30:49

服务端状态与数据获取库技术选型对比:React Query vs SWR vs Apollo Client vs RTK Query vs React Router

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
服务端状态与数据获取库技术选型对比:React Query vs SWR vs Apollo Client vs RTK Query vs React Router

服务端状态与数据获取库技术选型对比:React Query vs SWR vs Apollo Client vs RTK Query vs React Router

【免费下载链接】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

本文以本仓库(TanStack Query 一体化仓库)内 docs/framework/react/comparison.md 中的官方对比文档为核心骨架,对 React 生态中五个主流服务端状态方案进行逐维度横评,并深入 React Query 在本仓库中的源码实现(query-core 与 react-query 适配层),解释"表格中的每一项 ✅ 究竟靠什么代码实现"。读完本文,你将能读懂一张全量能力对比表背后的架构差异,理解 React Query 的确定性缓存序列化、结构共享、渲染追踪等设计如何落地,从而在真实项目中做出有依据的技术选型。

对比的定位与符号约定

该对比文档的初衷是"尽可能准确、尽可能无偏":表格结论面向所有被比较的库保持同一套标准,社区贡献者可通过页面底部的编辑入口在附上证据后修正信息。比较的五个对象分别是:

定位项目语境
React QueryTanStack Query 的 React 实现本文所在仓库 packages/react-query
SWRVercel 出品的轻量数据请求库外部独立项目(不在本仓库内)
Apollo ClientGraphQL 官方阵营客户端外部独立项目(不在本仓库内)
RTK QueryRedux Toolkit 内置数据获取方案外部独立项目(不在本仓库内)
React Router路由 + 数据加载(loader)外部独立项目(不在本仓库内)

对比表使用统一的 Feature/Capability Key 图例:

  • ✅ 一等公民、内置、开箱即用,无需额外配置或代码;
  • 🟡 支持,但需借助非官方的第三方或社区库/贡献实现;
  • 🔶 官方支持且有文档,但需要用户额外编写实现代码;
  • 🛑 未官方支持或未文档化。

定位、数据源与缓存架构横评

下表对比五者在"平台绑定、数据形态、缓存模型"上的底层差异。缓存架构的分歧是本次对比中最值得先读的部分,因为它决定了上层几乎所有能力的实现成本。

维度React QuerySWRApollo ClientRTK QueryReact Router
开源仓库(Star 徽章省略)TanStack/queryvercel/swrapollographql/apollo-clientreduxjs/redux-toolkitremix-run/react-router
平台依赖ReactReactReact、GraphQLReduxReact
官方横向比较页官方提供
支持的查询数据源Promise、REST、GraphQLPromise、REST、GraphQLGraphQL、Any(Reactive Variables)Promise、REST、GraphQLPromise、REST、GraphQL
支持框架ReactReactReact 及其他任意React
缓存策略分层键 → 值(Hierarchical Key→Value)唯一键 → 值规范化 Schema(Normalized)唯一键 → 值嵌套路由 → 值
缓存键策略JSONJSONGraphQL QueryJSON路由路径
缓存变更检测深比较键(稳定序列化)深比较键(稳定序列化)深比较键(不稳定序列化)键引用相等(===)路由变更
数据变更检测深比较 + 结构共享深比较(经由 stable-hash)深比较(不稳定序列化)键引用相等(===)Loader 运行
数据记忆化完整结构共享身份(===)规范化身份身份(===)身份(===)
包体积min+gzip 实时徽章(原表引用 Bundlephobia,本文不复现外链)同左同左同左react-router-dom + history 两包合计
API 定义位置组件内、外部配置均可组件内GraphQL Schema外部配置路由树配置

缓存架构差异解读

  • Hierarchical Key → Value(React Query):缓存以"查询键 + 查询"为单位组织,查询键支持任意层级嵌套;底层 QueryCache 把每个序列化后的键映射到一条查询状态。
  • Unique Key → Value(SWR / RTK Query):同样是键值缓存,但 React Query 的键结构更强调"前缀"式的层级可匹配性(详见下文 Partial Query Matching)。
  • Normalized Schema(Apollo Client):数据按实体扁平化存放(entity → record),以此规避高层数据重复,这也是其自动"变更新后重取"能力的基础。
  • Nested Route → Value(React Router):缓存生命周期与路由匹配绑定,离开路由即释放(见注解 8)。

值得注意的是"Cache Change Detection"列的差异:React Query 与 SWR 对键本身做稳定序列化 + 深比较,因此相同语义的键不会被重复缓存;Apollo 的序列化相对不稳定;RTK Query 使用键的**引用相等(===)**判断,缓存命中语义因此不同。

查询编排与常规数据能力横评

下表覆盖日常开发最常接触的查询编排能力。可以清晰看到 React Query 在此区间几乎全量 ✅,而 React Router 因"不缓存活动路由之外的数据",在持续性数据能力上大量为 🛑。

维度React QuerySWRApollo ClientRTK QueryReact Router
Queries(基础查询)
Cache Persistence(缓存持久化)🛑8
Devtools🛑
Polling/Intervals(轮询/定时刷新)🛑
Parallel Queries(并行查询)
Dependent Queries(依赖查询)
Paginated Queries(分页查询)
Infinite Queries(无限滚动查询)🛑
Bi-directional Infinite Queries(双向无限查询)🔶🔶🛑
Infinite Query Refetching(无限查询刷新)🛑🛑
Lagged Query Data(新旧数据平滑过渡)1
Selectors(选择器)🛑N/A
Initial Data(初始数据)
Scroll Recovery(滚动位置恢复)
Cache Manipulation(直接操作缓存)🛑
Outdated Query Dismissal(丢弃过期结果)
Stale While Revalidate🛑
Stale Time Configuration(staleTime 可配置)🛑7🛑🛑
Window Focus Refetching(窗口聚焦重取)🛑🛑
Network Status Refetching(网络恢复重取)🛑

缓存控制与渲染性能横评

下表聚焦"手动控制、渲染优化、离线与脱水平台能力",这些维度最能拉开各库架构差距。

维度React QuerySWRApollo ClientRTK QueryReact Router
Render Batching & Optimization(渲染批处理与优化)2🛑
Auto Garbage Collection(自动垃圾回收)🛑🛑N/A
Mutation Hooks
Offline Mutation Support(离线变更)🛑🟡🛑🛑
Prefetching APIs(预取 API)
Query Cancellation(查询取消)🛑🛑🛑
Partial Query Matching(部分查询匹配)3🔶N/A
Pre-usage Query/Mutation Configuration(用前预配置)4🛑
General Cache Dehydration/Rehydration(缓存脱水/注水)🛑
Offline Caching(离线缓存)🛑🔶🛑
React Suspense🛑
Abstracted/Agnostic Core(框架无关核心)🛑🛑
Automatic Refetch after Mutation(变更后自动重取)5🔶🔶
Normalized Caching(规范化缓存)6🛑🛑🛑🛑

八条关键差异注解:从"符号"到"源码"

表格之外,对比文档用 8 条注解释放了结论的前提与细节。下面逐条展开,并对 React Query 侧给出本仓库源码级佐证。

注解 1:Lagged Query Data —— 分页 UI 不闪硬加载态的关键

React Query 允许在新查询加载期间继续展示上一条查询的已有数据,避免分页/无限加载场景中出现硬 Loading 态(这也是 Suspense 未来要原生提供的体验)。其他库除非已预取,否则新查询期间会渲染硬加载态。该能力对分页与 Infinite UI 极其重要,源码中由"数据保留策略 + 状态派生"共同支撑(可关注 query-core 中 observer 对data/isPlaceholderData的状态组织)。

注解 2:Render Optimization —— 精确到属性的订阅式渲染

React Query 默认自动追踪组件实际访问了结果对象上的哪些字段,仅在这些字段变化时才触发重渲染。实现上,QueryObserver维护了一个属性追踪集合:

  • 定义#trackedProps = new Set<keyof QueryObserverResult>()(见 packages/query-core/src/queryObserver.ts#L65);
  • 通过trackResult返回一个Proxy包装的结果对象,get钩子里调用trackProp(key)把每次属性访问记入集合(见 packages/query-core/src/queryObserver.ts#L258-L273)。

若想关闭该优化,将notifyOnChangeProps设为'all':任何查询更新(新数据、fetching 状态变化等)都会重渲染组件。若只关心dataerror,可进一步设成['data', 'error']减少渲染次数。值得补充的是,该配置还支持函数形式,源码在通知分支中通过typeof notifyOnChangeProps === 'function' ? notifyOnChangeProps() : notifyOnChangeProps求值(见 packages/query-core/src/queryObserver.ts#L648-L662),可用于在渲染期间动态决定通知范围。

此外 React Query 会批量合并更新:当多个组件订阅同一条查询时,一次状态变更只触发一次应用层渲染。这意味着数据访问越"克制"、组件重渲染越少。

注解 3:Partial Query Matching —— 前缀语义 + 过滤函数任意操作查询组

由于 React Query 使用确定性查询键序列化,你可以在不逐一罗列具体键的情况下批量操作一组查询。例如:

  • 刷新所有以todos为键前缀的查询(无论携带何种变量);
  • 精确指定"带/不带变量、或含嵌套属性"的查询;
  • 甚至传入过滤函数,只匹配满足自定义条件的查询。

支撑它的底层函数是 packages/query-core/src/utils.ts#L236-L269 的partialMatchKey:它递归地只校验"被匹配方(b)中存在哪些键、a 对应位置是否与之相等",因此{ todos: [...] }这一前缀形态可以命中任意更深层的变体键。这是 SWR 仅能以 🔶(需自行实现)支持、而 React Query 能以一等能力提供的重要原因。

注解 4:Pre-usage Query/Mutation Configuration —— 把配置沉淀到"用之前"

这是"使用前即可配置好查询/变更行为"的能力别名。例如:一条查询可以预先配置好默认值(fetcher、staleTime、重试策略等),真正使用处只写useQuery({ queryKey }),无需每次重复传入 fetcher 或选项。SWR 只有全局默认 fetcher 的"部分形态",既不能按查询粒度配置,也谈不上为 mutation 配置。

在本仓库中,这一能力以多重形态落地:

  • queryOptions()/mutationOptions()工厂(见 packages/react-query/src/queryOptions.ts、packages/react-query/src/mutationOptions.ts),把查询定义与调用点解耦、同时获得完整类型推断;
  • 也可通过 QueryClient 的defaultOptions做全局/键粒度默认值,见 packages/query-core/src/queryClient.ts;
  • API 定义位置(表格第 11 行)中 React Query 标为 "Component, External Config",即组件内联定义与外部集中定义都受支持,正源于此。

注解 5:Automatic Refetch after Mutation —— "真正自动"依赖 Schema

要实现真正意义上的"变更后自动重取",库需要依赖Schema(例如 GraphQL 提供的 schema)及识别实体的启发式规则,才能知道一次变更影响了哪些实体、进而重取相关查询。因此 Apollo(基于 GraphQL Schema + 规范化缓存)能标 ✅,而 React Query、SWR、RTK Query 均不提供这种魔法式的全自动刷新;React Query 给出的路径是invalidateQueries等显式缓存失效 API,属于"半自动、可控性优先"的设计哲学(标 🔶)。

注解 6:Normalized Caching —— 刻意不做的规范化

React Query、SWR、RTK Query目前都不支持自动规范化缓存(即以扁平实体存储避免高层数据重复)。React Query 的定位是把"数据获取 + 缓存生命周期"做好,实体关系建模仍交给开发者(如用 selector 做 denormalize)。这也解释了表格中 React Query 的 Cache Persistence、Dehydration/Rehydration、离线能力为何全部 ✅ —— 因为它缓存的是完整查询响应而非依赖 Schema 的碎片实体,天然易于整体持久化。

注解 7:SWR 的 Immutable Mode —— 不替代 staleTime

SWR 自带 "immutable" 模式,可让查询在缓存生命周期内只 fetch 一次,但它没有 stale-time 概念,也没有条件式自动重新验证。也就是说,SWR 无法表达"数据在 X 毫秒内视为新鲜、过期后按需重取"这类时间窗口语义;React Query 的staleTime与 RR(stale-while-revalidate)模型是独立的、按查询可配置的。

注解 8:React Router 的缓存持久化边界

React Router不会缓存超出"当前匹配路由"之外的数据:一旦离开某条路由,其 loader 数据即被丢弃(Nested Route → value 模型决定),因此没有跨页面缓存,Cache Persistence、Devtools、轮询、Infinite、Cache Manipulation 等持续型能力自然为 🛑。

React Query 优势特性在源码中的落点

对选型者而言,看懂"React Query 为什么能在多数行拿 ✅"比背表格更有价值。以下能力在本仓库均可直接定位到实现文件。

确定性缓存键:稳定序列化的hashKey

React Query 的缓存键变更检测依赖键本身被稳定序列化。默认哈希函数 packages/query-core/src/utils.ts#L219-L234 的hashKey实现方式是:对值做JSON.stringify,且在 stringify 的 replacer 中对每个普通对象按键名排序后再输出。因此{ b: 1, a: 2 }{ a: 2, b: 1 }会生成相同哈希——相同语义的查询键被当作同一缓存条目;开发者还可通过queryKeyHashFn注入自定义哈希。这正是表格 "Cache Change Detection: Deep Compare Keys (Stable Serialization)" 的源码依据。

数据记忆化:replaceEqualDeep的结构共享

React Query 的 Data Memoization 为 "Full Structural Sharing":新数据与旧数据深比较后,能复用的子树直接复用旧引用,避免无意义重渲染。核心函数是 packages/query-core/src/utils.ts#L273 起的replaceEqualDeep

  • a === b(引用相等或原始值相等)直接返回a
  • 深比较失败时,仅替换 b 中与 a 不等的子节点,其余子节点保持 a 的引用。

该函数在 utils.test.tsx 中有成组的单元测试覆盖(含原始值、Date、数组、对象、undefined 等各种边界),并可通过查询选项structuralSharing: false关闭,或传入自定义函数(见 queryClient.test.tsx 对structuralSharing自定义的验证)。

渲染追踪与批量更新:Proxy +notifyOnChangeProps

如注解 2 所述,trackResult以 Proxy 记录属性访问(见 queryObserver.ts#L258-L273),QueryObserver在通知时比对"本次实际变化字段"与"组件访问过的字段集合"。相关状态分支集中在 queryObserver.ts#L648-L662,支持'all'、字段数组、函数三种形态。React 适配层(见 packages/react-query/src/useBaseQuery.ts)负责在渲染期间把追踪到的属性传给 observer,形成端到端的按需渲染闭环。

自动垃圾回收:gcTime驱动的 Query 生命周期

React Query 的 Auto Garbage Collection 由 Query 自身的生命周期管理实现:查询在失去所有订阅者后进入倒计时,gcTime(原cacheTime)到期即被清理并释放订阅。源码在 packages/query-core/src/query.ts#L211 通过updateGcTime(this.options.gcTime)将选项写入 query 实例,QueryCache 据此调度回收。这解释了为何 React Query 既能"用后即焚"控制内存,又能配合持久化中间件做离线长期留存。

双向无限查询:getNextPageParam/getPreviousPageParam

Infinite Queries 及"双向"能力由 packages/query-core/src/infiniteQueryBehavior.ts 支撑:前向翻页读取getNextPageParam的返回并推进pageParam,后向翻页则基于getPreviousPageParaminitialPageParam从已有首页向前补页(见该文件 L132-L152 的取参逻辑),并对外暴露hasNextPage/hasPreviousPage判断(L164-L175)。React 层入口为 packages/react-query/src/useInfiniteQuery.ts。SWR/Apollo 需要自行组合实现的方向性逻辑,在这里是内置语义。

取消、预取、缓存操作:QueryClient的统一 API

表格中 Query Cancellation、Prefetching、Cache Manipulation、Dehydration/Rehydration 等 ✅ 项,都收敛在 packages/query-core/src/queryClient.ts 暴露的prefetchQueryfetchQuerycancelQueriesremoveQueriesinvalidateQueriessetQueryDatagetQueryCache().find/findAll等 API 上。其行为在 packages/query-core/src/tests/queryClient.test.tsx 中由大量测试锁定,例如prefetchQuery后的缓存命中(L1851 附近)、removeQueries精确/前缀删除(L1941 附近)、cancelQueries配合revert选项(L1986、L2003 附近)。React 侧另有独立于组件的usePrefetchQuery/usePrefetchInfiniteQuery(见 packages/react-query/src/usePrefetchQuery.tsx)。

持久化、Devtools 与框架无关核心

  • 持久化 / 离线persistQueryClient系列位于 packages/react-query-persist-client/src,底层契约定义在 packages/query-persist-client-core/src,配合 packages/query-sync-storage-persister 等存储适配器即可实现 Cache Persistence、Dehydration/Rehydration 与 Offline Caching。
  • Devtools:独立包 packages/react-query-devtools/src,另有 packages/query-devtools 承载核心面板,Devtools 一行 ✅。
  • Abstracted/Agnostic Core:这是 React Query 架构上区别于 SWR/React Router 的关键点。本仓库根下 packages/query-core 是纯框架无关核心,而 packages/react-query、packages/solid-query、packages/svelte-query、packages/vue-query、packages/preact-query、packages/angular-query-experimental 只是薄薄的框架适配层。查询键、缓存、垃圾回收、重试、轮询等复杂逻辑只写一遍,各框架共享同一套测试与语义——这正是对比文档在同仓库语境下称 React Query 具备 "Abstracted/Agnostic Core" 的直接依据。

如何利用这份对比做决策

把表格与注解还原为决策建议时,请基于自身约束而非"谁的行最多":

  • 需要类 Schema 规范化缓存 + GraphQL 深度集成的项目→ Apollo Client 的 Normalized Caching 与变更后自动重取是结构性优势;
  • 已经全面使用 Redux、希望状态与数据获取同源管理→ RTK Query 与 Redux 生态集成最顺滑,且同样是外部配置式 API;
  • 团队只需一个极轻量的 fetch-on-render 层→ SWR 体积与心智负担更小,但要接受没有 staleTime、GC、Query Cancellation 等进阶能力;
  • 数据生命周期应当跟随路由、以 loader 驱动页面数据→ React Router 的嵌套路由数据模型最匹配,但要清楚"离开路由即失缓存"的边界(注解 8);
  • 需要 Server-State 完整生命周期(缓存、失效、后台刷新、GC、持久化、并发/依赖/无限查询、Devtools)且希望知识沉淀为框架无关资产→ React Query 的能力矩阵与架构(QueryClient API、queryOptions 预配置、partial key 失效、structural sharing、Suspense 支持等)提供了当前对比中最完整且最可组合的集合。

需要特别强调的是:任何第三方能力声明(例如 SWR 内部基于stable-hash的深比较)均以各库官方文档与源码为准;本文中的 React Query 侧结论均有上文标注的本仓库文件可作为一手证据。同时,"无偏"也意味着承认其边界——如注解 6 所述,React Query刻意不提供自动规范化缓存,这与 Apollo 是取舍而非高下。

结语

一份横向对比表的真正价值,不在于"谁赢了几行 ✅",而在于它逼你直面五个关键架构分叉:缓存是键值、层级还是规范化?数据生命周期绑定查询还是路由?序列化是否稳定、变更检测是深比较还是引用相等?更新是否批量、渲染是否可按属性订阅?核心是否框架无关、能力是否可跨框架复用?以本仓库 docs/framework/react/comparison.md 为基础,结合 query-core 的源码实现,你可以把表中的每一个符号都还原成"一段可以阅读、可以测试、可以评估的真实设计",从而为自己的项目做出有依据的工程决策。

【免费下载链接】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/9 20:24:07

STM32F407贪吃蛇实战:FSMC驱动与状态机设计解析

简介&#xff1a;面向STM32F407及嵌入式初学者的贪吃蛇游戏完整工程&#xff0c;基于Cortex-M4内核与HAL库开发&#xff0c;可帮助理解中断管理、定时器应用、GPIO显示控制、USART通信及碰撞检测等实时系统关键环节。代码采用模块化组织&#xff0c;配合STM32CubeMX生成的初始化…

作者头像 李华
网站建设 2026/9/9 20:23:45

湖南单招两个志愿可以填同一所学校吗

湖南省高职单招设置第一志愿、第二志愿两个院校志愿&#xff0c;不少学生和家长在准备志愿填报时&#xff0c;会产生一个想法&#xff1a;如果十分看好某一所高职院校&#xff0c;能不能把第一志愿和第二志愿都填写成这同一所学校&#xff0c;增加录取机会。很多家长认为&#…

作者头像 李华
网站建设 2026/9/9 20:23:27

27个经典趣味C++程序:从语法到算法实战的完整学习指南

简介&#xff1a;一套面向C初学者的经典趣味程序合集&#xff0c;共27个示例&#xff0c;按基础到进阶组织&#xff0c;覆盖输出语句与程序结构、数据类型与变量、数组与字符串、指针、面向对象、STL容器与算法、函数重载、异常处理、文件I/O与模板编程等&#xff0c;并穿插简单…

作者头像 李华
网站建设 2026/9/9 20:22:21

ijkplayer 0.8.8 Android .so编译与集成实战指南

简介&#xff1a;这套以.so动态库形式提供的ijkplayer 0.8.8编译产物&#xff0c;来自B站开源的跨平台播放框架&#xff0c;面向Android、iOS等移动端开发者&#xff0c;重点解决自行编译步骤繁琐、依赖库难匹配的问题&#xff0c;适合已有NDK与播放器接入经验的技术人员直接取…

作者头像 李华