为什么要用 React query

date
Nov 4, 2025
slug
why-use-react-query
status
Published
tags
React Query
summary
为什么要用 React Query
type
Post
嘿,各位紧追不舍的小伙伴们!你们的“老司机”工程师又上线啦。
今天咱们来聊一个灵魂拷问:为啥有些技术就火了,有些就凉了?
原因肯定五花八门,但我这儿有个歪理,我管它叫 “五点钟原则”
啥意思呢?就是说,一个解决问题的技术方案,它的抽象层次会一直进化,直到能让咱们这些“普通”开发者,在下午五点钟之前,心安理得地关掉 Jira 工单,打卡下班回家。
你可能会说:“喂,这太功利了吧!技术的优雅、灵活性、有没有漏洞,这些不重要吗?”
重要,但没那么重要。对大多数一线码农来说,按时下班就是最大的优雅。
我知道这听起来有点刺耳,但绝对公平。不信你看,有个叫 is-string 的库,功能就一行代码的事儿,每周下载量 2800 万次。这背后,是 2800 万次“我懒得想,用了再说”的决绝。
当然,当一个抽象方案既优雅,又能让你准时下班时,那简直就是魔法!
接下来,我要给你们讲一个我最喜欢的、完美符合“五点钟原则”的魔法故事。
故事的主角,是犹他州一个小镇上的独立开发者。他在业余时间,捣鼓出了一个库,现在每六个 React 应用里就有一个在用它。具体点说,它每周下载量 330 万次,就在你读这篇文章的这几十秒里,它又被下载了三百多次。
这个库,当然就是大名鼎鼎的 React Query
为了说明白它是如何满足“五点钟原则”的,咱们得先看看它帮我们干掉了哪个“大魔王”。
信不信由你,这个大魔王,就是 React 本身
要搞明白为啥,咱们得从盘古开天辟地说起。
React 的核心思想,简单到可以用一个公式概括:视图 = f(状态),也就是 view = function(state)
你只管关心状态(State)怎么变,剩下的交给 React 就行。这套哲学的主要载体就是“组件”,它把 UI 和相关的状态、逻辑打包在一起,让你能像拼乐高一样拼凑出复杂的界面。
这套逻辑在拼 UI 的时候,简直是天作之合。
但问题是,现实世界的应用不只有 UI。我们还需要组合和复用那些看不见的逻辑。
于是,React Hooks(钩子) 登场了,它就是为了解决这个问题而生的。
Hooks 的发布,开启了 React 的一个新时代——我愿称之为 “我到底该TM怎么获取数据啊?” 的时代。
你翻遍 React 官方自带的 Hooks,会发现一个有趣的事实:没有一个 Hook 是专门为“数据获取”这个最最最常见的场景设计的。
在 React 里,最接近“原生”的数据获取方式,就是在 useEffect 里用 fetch,然后用 useState 存结果。
你肯定见过下面这种“新手村”代码:
但问题是,你不能在工作中写这种“新手村”代码!

第一关:加载状态缺失

你一刷新页面,数据还没回来,整个布局“啪”地一下,跳了!这就是“累积布局偏移”(CLS),用户体验的两大杀手之一。
怎么办?加个 isLoading 状态呗。请求开始时是 true,结束时是 false。加载时显示个骨架屏。

第二关:错误处理缺失

如果网络抽风,API 挂了,我们的应用会咋样?它会陷入“无限加载菊花转”,用户体验的另一大杀手。
行,再加个 error 状态。
我们用 useEffect,告诉 React:“嘿,id 变了,你就去同步一下宝可梦的数据。” 这一招,把构建 Web 应用中最复杂的“异步副作用”管理,变成了看似简单的实现细节。
然而...我们还没走出新手村。说真的,现在的代码里藏着一个最恶心的 Bug:它不易察觉,但又极其浪费。
你发现了吗?

第三关:竞态条件(Race Condition)

fetch 是个异步请求,鬼知道它要花多长时间。
想象一个场景:用户手速飞快,先点了“妙蛙种子”(id=1),请求发出去了。网速有点慢,在等的时候,他又点了“皮卡丘”(id=25),第二个请求也发出去了。
现在我们有两个请求在路上。
最要命的是,我们不知道哪个会先回来!万一“皮卡丘”的请求先完成了,setPokemon 会把页面更新成皮卡丘。紧接着,“妙蛙种子”那个慢悠悠的请求终于回来了,setPokemon 又会把页面更新成妙蛙种子。
最终,用户明明点的是皮卡丘,看到的却是妙蛙种子。UI 和用户的意图完全不一致!这就是竞态条件
这体验,稀碎。怎么解决?
答案是: useEffect 的兔子洞里钻得更深。
我们需要告诉 React:“只认最新的那个请求,其他的都当没发生过!”
useEffect 有个“隐藏款”功能:清理函数。如果你从 useEffectreturn 一个函数,React 会在下一次 effect 执行前,以及组件卸载时,调用这个清理函数。
我们可以利用这个机制!
每当 id 变化,新的 effect 运行时,旧 effect 的清理函数会执行,把旧 effect 内部的 ignore 变量变成 true。这样,即使旧的请求回来了,它看到的 ignore 也是 true,于是乖乖地啥也不干。
好家伙,我们靠着 JavaScript 闭包和 useEffect 的精妙设计,干掉了竞态条件。
到这儿,总算可以提交 PR了吧?😅
如果你真这么干了,你的同事八成会说:“兄弟,把这段逻辑抽成一个自定义 Hook 吧,别到处复制粘贴。”
你说得对!于是你大笔一挥,写出了一个通用的 useQuery Hook。
我还记得我第一次写出这种抽象时,那种油然而生的自豪感。我觉得自己改变了世界!
直到我开始真正使用它……

第四关:数据重复获取

我们的自定义 Hook 没解决一个根本问题:数据是组件本地的
组件 A 需要宝可梦数据,它 fetch 一次。
组件 B 也需要同样的数据,它又 fetch 一次。
每个组件都有自己的 isLoading,于是页面上出现两个“加载菊花”。
更糟的是,组件 A 的请求可能成功了,组件 B 的却失败了。或者,两个请求拿回来的数据不一致(比如一个 issue 状态是 open,另一个是 closed)。
React 辛辛苦苦带来的“可预测性”,瞬间化为乌有。

第五关:状态提升与 Context 的陷阱

有经验的开发者会说:“简单!把状态提升到父组件,或者用 Context 做全局状态!”
好主意!咱们用 Context 改造一下 useQuery
现在,所有组件共享一份数据,数据一致性回来了。我们用一个全局对象,以 url 为 key,缓存了所有请求的数据、加载和错误状态。
嗯,它确实能工作——但这段代码,是未来的你最想穿越回来掐死现在的你时会写下的那种代码。
为啥?
  1. 性能灾难:React Context 有个“臭毛病”,它无法让组件只订阅状态的一部分。任何一个调用 useQuery 的组件,都订阅了整个 queryContext。这意味着,只要任何一个 URL 的数据发生变化,所有用到了 useQuery 的组件都会重新渲染! 一个组件打喷嚏,所有组件都跟着感冒。
  1. 重复请求依旧:如果两个组件在同一时间挂载,它们还是会各自触发 useEffect,发出两个一模一样的请求。我们只是在共享结果,并没有解决重复请求本身。
  1. 缓存失效:既然有了缓存,我们就得考虑什么时候让它失效。欢迎来到计算机科学两大难题之一的现场(另一个是命名)。
看到没?我们从一个简单的数据获取需求出发,一路打怪升级,最终把自己困在了一个由 useStateuseEffectContext 联手打造的、充满复杂性的“技术棺材”里。
这不能怪任何一个单一的技术。useEffect 本身就令人困惑,Context 容易变得一团糟,把它们混在一起就是自找麻烦。
但最根本的问题是:我们一直在用处理“同步状态”的思路,去处理“异步状态”。
  • 同步状态(客户端状态):就像你钱包里的钱。它是“你的”,立即可用,除了你自己没人能动它。它非常可预测。
  • 异步状态(服务器状态):就像你在银行的存款。它不是“你的”,它在别处(服务器/数据库),你得去“取”才能用。它随时可能被别人修改(比如你的伴侣也有一张副卡)。它难以预测。
我们有 useState, useReducer, Redux, Zustand 等无数工具来管理客户端状态。
但管理服务器状态呢?在 React Query 出现之前,我们的选择……寥寥无几。
讽刺的是,你可能听过 React Query 是“React 中缺失的数据获取库”。
这句话大错特错!

React Query 不是一个数据获取库。

这是一件天大的好事!因为现在我们应该清楚了,获取数据本身并不难,难的是在时间的长河中管理这些数据。
React Query 更准确的描述是:一个为 React 量身定做的异步状态管理器。
它压根不帮你获取数据。你给它一个能返回 Promise 的函数(用 fetch, axios, graphql 都行),它负责接管这个 Promise 解析后的数据,并把它变成在整个应用中可预测、可共享、高性能的“服务器状态”。
它在后台,默默地帮你处理了所有那些你没意识到、或者压根不想去思考的脏活累活:
  • 缓存
  • 竞态条件处理
  • 请求去重
  • 后台自动更新
  • 数据过期与垃圾回收
  • ……等等等等
而最美妙的是什么?
你再也不用费心去琢磨 useEffect 那个该死的依赖数组和清理函数到底是怎么工作的了!
这就是为什么它完美满足了 “五点钟原则”。它提供了一个足够高级、足够省心的抽象,让你把精力从底层的泥潭里解放出来,去关注真正的业务逻辑。然后,在下午五点钟,安心地关闭 Jira,打卡下班。
好了,今天的“技术故事会”就到这里。是不是感觉,那些让你头疼不已的问题,突然间就云开雾散了?

© 拾光 2025 - 2026

粤ICP备2025472574号