React Query 如何将一个 React Native 应用程序的 API 层代码量减少 500 行
一个真实的 React Native 案例研究,展示了如何通过从手动使用 useEffect 进行数据获取转向 React Query,从而消除重复代码,并提升缓存功能与离线处理能力。
简介
不久前,一个你可能熟悉的 React Native 项目遇到了一个常见的问题。
几乎所有需要获取远程数据的界面都遵循相同的模式:
- 在
useEffect中实现数据获取逻辑 - 多个独立的加载状态指示器
- 自定义的错误处理代码块
- 手动实现的下拉刷新逻辑
- 人工设计的重试机制
- 临时的本地缓存解决方案
从功能上来看,这些做法并没有问题。但在数十个界面中保持一致性却成了巨大的维护负担。
切换到 TanStack 的 React Query 后,团队得以剔除大量重复的网络代码,同时获得了更出色的缓存功能、更清晰的加载管理以及更可靠的离线表现。该库内置了查询缓存、自动后台重取功能以及智能网络处理逻辑,大大减少了手动编写状态管理代码的需求。
下文将通过实际 React Native 应用界面中的案例,对比传统手动实现方式与 React Query 版本的区别。
传统 API 管理方式的问题
典型界面的初始化结构大致如下:
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
try {
setLoading(true);
const response = await api.getPosts();
setData(response);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
这种样板代码在整个应用中反复出现。
每个界面都需要独立管理:
- 加载状态标志
- 错误状态标志
- 重试处理逻辑
随着应用规模的不断扩大,这种重复的模式越来越难以保持一致性。
React Query 的出现
使用 React Query 重写后的同一界面,简化为如下形式:
const { data, isLoading, error, refetch } = useQuery({
queryKey: ["posts"],
queryFn: fetchPosts,
});
这就是全部的实现代码。
React Query 会自动处理以下所有功能:
- 加载状态指示
- 错误状态处理
- 去重重复的请求
- 在后台重新获取数据
- 缓存管理
- 重试失败的请求
- 响应网络重新连接
这些功能大多开箱即用,还可以通过 staleTime、gcTime、重试配置以及重新获取数据设置等选项进行调优。
对比一:网络请求
使用 React Query 之前
想象有三个独立的屏幕,都需要相同的用户资料数据。
由于没有共享的缓存层,每个屏幕都会单独发起请求:
Profile Screen → API Call
Settings Screen → API Call
Dashboard Screen → API Call
结果:为相同的数据进行了三次独立的网络调用。
使用 React Query 之后
Profile Screen → API Call
Settings Screen → Cached Data
Dashboard Screen → Cached Data
这次的结果:仅需要一次网络调用。
React Query 会将结果存储在查询键下,并将该缓存数据共享给所有请求该数据的组件。之后任何请求相同键的屏幕都能立即获取到缓存值,同时后台的自动刷新功能还能默默保持数据最新。
实际应用效果
在用户频繁访问的屏幕上,这会带来以下好处:
- 发送到 API 的重复请求大幅减少
- 后端服务器的负载降低
- 屏幕之间的切换更加流畅
对比 #2:缓存效率
可以说,缓存是 React Query 发挥最大价值的地方。
当相同的查询在其缓存副本失效之前被再次请求时:
useQuery({
queryKey: ["products"],
queryFn: getProducts,
staleTime: 300000,
});
界面可以立即显示缓存数据,React Query 可选择在后台静默地刷新它。缓存条目会被保留下来,并最终根据您设定的规则进行垃圾回收。
实际示例
以一个电商应用的产品列表页面为例。
如果没有缓存,打开该页面意味着:
Open Products
↓
Network Request
↓
Navigate Back
↓
Open Products Again
↓
Network Request
使用 React Query 后,同样的流程变为:
Open Products
↓
Network Request
↓
Navigate Back
↓
Open Products Again
↓
Instant Cached Data
不同之处在于,使用该应用的用户会明显感受到更流畅的体验。
对比 #3:加载状态
在采用 React Query 之前,要跟踪加载状态就需要处理多个布尔值:
const [loading, setLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const [isRetrying, setIsRetrying] = useState(false);
之后,只需一次钩子调用即可获取所需的一切:
const {
isLoading,
isFetching,
isRefetching,
} = useQuery(...)
React Query 能区分首次加载与后续的背景请求,这使得相关 UI 逻辑更易于理解。
实际好处
应用无需在每次请求时都显示全屏加载器,而是可以区分不同情况:
- 初始加载:全屏加载器
- 背景刷新:小型且不显眼的加载指示器
- 数据已缓存:完全没有可见的干扰
这样能让使用应用的用户感受到更出色的响应速度。
对比 #4:离线支持
团队往往容易低估离线功能的重要性。
手动处理这种方式通常如下:
Check Connectivity
Pause Requests
Retry Later
Handle Errors
Refetch On Reconnect
这意味着需要在整个代码库中分散编写专门的连接逻辑。
React Query 提供了内置的在线与离线管理功能,同时还具备能响应重新连接事件的重新获取机制。在 React Native 中,你可以结合平台的网络状态监听器使用 onlineManager 来实现这一功能。
例如:
onlineManager.setEventListener(...)
React Query 还可以配置为以离线优先的模式运行,从而相应地调整其网络行为。
实际示例
以新闻阅读应用为例。
在一天之内,用户的连接状态可能如下:
- 早晨:在线
- 下午:离线
- 晚上:重新上线
在这段时间内,之前缓存的文章依然可以正常阅读,而一旦恢复连接,新内容就能自动同步。
实际应用场景
1. 新闻类应用
这样做的好处:
- 缓存的文章
- 自动后台刷新
- 降低整体API流量
- 离线时仍可继续阅读
2. 电子商务应用
其带来的好处:
- 缓存产品列表
- 提前预取分类信息
- 更快地在各板块之间切换
- 显著提升购物体验的流畅度
React Query还支持在切换页面之前就预取数据,从而缩短等待时间。
3. 控制面板应用
其带来的好处:
- 能自动刷新的组件
- 在多个界面之间共享缓存
- 减少网络通信量
- 整体状态管理更为简单
这种模式尤其适合分析控制面板和管理员界面。
我们实际移除了哪些内容
迁移完成后:
已移除
- 自定义的加载状态处理机制
- 手动重试代码
- 重复的API调用
- 下拉刷新的模板代码
- 自制的缓存逻辑
- 手动重新获取数据的管理功能
已添加
- React Query库本身
- 查询键
- 一个
QueryClient实例
最终效果是:大约500行的API处理代码不再需要。
何时不需要使用React Query
在以下情况下,使用React Query可能有些过度:
- 您的应用仅进行少量API调用
- 底层数据几乎不会变化
- 确实不需要缓存功能
- 离线状态对您的应用而言无关紧要
不过,对于大多数生产级应用而言,其带来的好处往往很快就能超过最初的学习成本。
总结
React Query并不仅仅是另一种获取数据的方式。
它实际上是一种完整的服务器状态管理解决方案,不仅能减少重复的API代码,还能提升缓存效率、加载表现、网络利用率以及离线使用体验。
这里最大的收获并非单纯的性能提升,而是复杂度的降低。
代码更少,错误更少,使用应用的用户体验也更好。
正是这种综合优势使得进行迁移变得值得。