10.SWR 与 React Query:它们到底解决了什么问题? 来源:https://w0hog67yl81.feishu.cn/wiki/CzHuwMFsPiB35hkZPPOc3lXHnlg 采集状态:已完成正文采集;正文滚动至底部,按块及代码行号采集 ​ 在现代前端开发中,获取和管理远端数据是构建用户界面的核心环节。当我们谈论 React 应用中的数据获取时,useEffect 和 useState 的组合是许多人首先想到的方案。然而,随着应用复杂度的提升,我们很快会发现这个“经典组合”显得力不从心。​ ​ 这正是 SWR 和 React Query (现已更名为 TanStack Query) 等库大放异彩的地方。它们并非简单地替换 fetch 或 axios,而是提供了一套精密的策略,专门用于管理“服务端状态 (Server State)”。为了理解它们的价值,我们首先需要回到问题的起点:传统数据获取方式究竟有什么痛点?​ ​ 一、起点:useEffect 与 useState 的困境​ ​ 我们来看一个典型的数据获取场景:在组件挂载时请求一篇文章数据,并处理加载和错误状态。​ ​ import React, { useState, useEffect } from 'react'; function Article({ id }) { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { // 确保组件挂载时才执行 let isMounted = true; const fetchData = async () => { setIsLoading(true); try { const response = await fetch(`/api/articles/${id}`); if (!response.ok) { throw new Error('网络请求失败'); } const result = await response.json(); if (isMounted) { setData(result); } } catch (e) { if (isMounted) { setError(e); } } finally { if (isMounted) { setIsLoading(false); } } }; fetchData(); // 清理函数,防止组件卸载后更新状态 return () => { isMounted = false; }; }, [id]); // 依赖 id,当 id 变化时重新请求 if (isLoading) return
加载中...
; if (error) return
发生错误: {error.message}
; return (

{data.title}

{data.content}

); } ​ 这段代码看起来功能完备,但其中隐藏着不少问题:​ ​ 1. 模板代码冗余 (Boilerplate):每个需要获取数据的组件,几乎都需要重复实现 data, isLoading, error 这三组状态及其管理逻辑。​ 2. 缺乏缓存机制:如果用户切换到其他页面再切回来,即使 id 不变,组件也会重新挂载并再次发送相同的网络请求,造成资源浪费和不必要的等待。​ 3. 数据陈旧 (Stale Data):一旦数据获取成功,它就静静地待在 data 状态里。如果此时数据库中的这篇文章被其他用户修改了,我们的界面将毫无察觉,展示着过时的数据。​ 4. 竞态条件 (Race Conditions):当 id 快速变化时(例如,用户在文章列表间快速切换),前一个请求可能比后一个请求更晚返回,导致 setData 写入了错误(旧的)数据。虽然上面代码中的 isMounted 标志可以缓解一部分卸载后的问题,但并不能完全解决请求顺序错乱的竞态问题。​ 5. 体验不佳:应用在后台重新激活(用户切换浏览器标签页回来)、或者断网后重连时,我们往往希望数据能自动刷新,以确保其新鲜度。手动实现这些逻辑会非常复杂。 这些问题共同指向一个核心:**手动管理异步的服务端状态既繁琐又容易出错**。​ ​ 二、核心思想:将服务端数据视为需要同步的状态​ ​ SWR 和 React Query 的设计哲学,是把从服务器获取的数据看作一种特殊的、需要与远端“同步”的状态,而不是一次性的操作。这与 React 管理的“客户端状态” (Client State) 有着本质区别。​ ​ • 客户端状态:由用户直接操作,是同步且可预测的(例如,表单输入、开关状态)。​ • 服务端状态:来自远端,是异步的、不受我们完全控制的,并且可能随时被其他人修改。 因此,对于服务端状态,我们需要的不是简单的“获取”,而是一整套**缓存、更新、同步**的机制。​ ​ SWR 这个名字本身就是其核心策略的缩写:**Stale-While-Revalidate**。​ ​ Stale-While-Revalidate:这是一种缓存策略。当请求数据时,库会立即从缓存中返回“陈旧 (Stale)”的数据(如果存在),让 UI 能够瞬时渲染。与此同时,它在后台发起一个“重新验证 (Revalidate)”的请求,待获取到最新数据后再悄无声息地更新 UI。 ​ 这种策略兼顾了性能和数据一致性:用户能快速看到内容,同时又能保证内容最终会更新到最新。​ ​ 三、SWR / React Query 解决了哪些具体问题?​ ​ 基于上述核心思想,这些库为我们解决了 useEffect 方案中的所有痛点。​ ​ 1. 缓存与去重 (Caching & Deduplication)​ ​ 这是最基础也是最重要的功能。当你使用 useSWR 或 useQuery 并传入一个唯一的 key (通常是 URL 或一个数组) 时:​ ​ • 首次请求:数据被获取并存入全局缓存。​ • 后续请求:如果其他组件使用相同的 key 请求数据,库会直接从缓存中返回数据,而不会发起重复的网络请求。这极大地减少了不必要的 API 调用。 效果:应用加载更快,服务端压力更小。​ ​ 2. 状态管理简化 (Simplified State Management)​ ​ 我们不再需要手动管理 isLoading, error, data。库已经帮我们封装好了。​ ​ 使用 React Query 的例子:​ ​ import { useQuery } from '@tanstack/react-query'; const fetchArticle = async (id) => { const response = await fetch(`/api/articles/${id}`); if (!response.ok) { throw new Error('网络请求失败'); } return response.json(); }; function Article({ id }) { const { data, isLoading, error } = useQuery({ queryKey: ['article', id], // 唯一的 key queryFn: () => fetchArticle(id), }); if (isLoading) return
加载中...
; if (error) return
发生错误: {error.message}
; return (

{data.title}

{data.content}

); } ​ 对比之前的 useEffect 版本,代码量大幅减少,逻辑也变得极为清晰。我们只需声明“需要什么数据 (queryKey)”和“如何获取 (queryFn)”,其余的加载、错误处理、数据存储都由库自动完成。​ ​ 3. 自动重新验证 (Automatic Revalidation)​ ​ 为了解决数据陈旧问题,这些库会在特定时机自动触发数据刷新,确保界面信息与服务端保持同步。默认的触发时机包括:​ ​ • 窗口聚焦时 (Revalidate on Focus):用户切换到其他应用再切回来,数据会自动刷新。​ • 网络重连时 (Revalidate on Reconnect):设备从离线状态恢复在线,数据会自动刷新。​ • 组件挂载时 (Revalidate on Mount):组件重新挂载时,会检查缓存数据是否过期并决定是否刷新。​ • 可配置的轮询 (Optional Interval Polling):可以设置一个时间间隔,定时刷新数据(例如,股票价格、监控仪表盘)。 效果:应用具有“实时感”,用户总能看到相对较新的数据,而开发者无需编写任何额外代码。​ ​ 4. 复杂场景的内置支持​ ​ 除了上述核心功能,这些库还内置了对许多高级场景的优雅支持:​ ​ • 分页 (Pagination) 和无限滚动 (Infinite Loading):提供了 useInfiniteQuery 等专用 Hooks,能极大地简化加载“下一页”数据的逻辑和状态管理。​ • 乐观更新 (Optimistic Updates):在修改数据的操作中(如点赞、评论),可以先假设请求会成功并立即更新 UI,然后再向服务器发送请求。如果请求失败,UI 会自动回滚到原始状态。这带来了极致流畅的交互体验。​ • 请求重试 (Request Retries):当网络请求失败时,库可以自动进行有限次数的重试。 结论​ ​ SWR 和 React Query 并非简单的数据请求工具,它们是**专为现代 Web 应用设计的服务端状态管理器**。它们解决的核心问题,是从手动、繁琐、易错的 useEffect 数据获取模式,转向一种自动、高效、健壮的声明式数据同步模型。​ ​ 通过引入**缓存、自动重新验证、状态简化**等核心机制,它们帮助我们:​ ​ • 提升开发效率:减少样板代码,专注于业务逻辑。​ • 改善用户体验:通过缓存和 Stale-While-Revalidate 策略让应用响应更快,通过自动刷新让数据更“新鲜”。​ • 增强应用健壮性:内置处理了竞态条件、错误重试、网络变化等复杂边界情况。 在今天,对于任何需要与服务端进行数据交互的非小型 React 应用而言,采用 SWR 或 React Query 已经不是一个“选项”,而是一种能够显著提升项目质量和开发体验的最佳实践。​ ​