Skip to content

SWR 与 React Query:它们到底解决了什么问题?

在现代前端开发中,获取和管理远端数据是构建用户界面的核心环节。当我们谈论 React 应用中的数据获取时,useEffectuseState 的组合是许多人首先想到的方案。然而,随着应用复杂度的提升,我们很快会发现这个“经典组合”显得力不从心。

这正是 SWR 和 React Query (现已更名为 TanStack Query) 等库大放异彩的地方。它们并非简单地替换 fetchaxios,而是提供了一套精密的策略,专门用于管理“服务端状态 (Server State)”。为了理解它们的价值,我们首先需要回到问题的起点:传统数据获取方式究竟有什么痛点?

一、起点:useEffectuseState 的困境

我们来看一个典型的数据获取场景:在组件挂载时请求一篇文章数据,并处理加载和错误状态。

javascript
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 <div>加载中...</div>;
  if (error) return <div>发生错误: {error.message}</div>;

  return (
    <div>
      <h1>{data.title}</h1>
      <p>{data.content}</p>
    </div>
  );
}

这段代码看起来功能完备,但其中隐藏着不少问题:

  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 方案中的所有痛点。

缓存与去重 (Caching & Deduplication)

这是最基础也是最重要的功能。当你使用 useSWRuseQuery 并传入一个唯一的 key (通常是 URL 或一个数组) 时:

  • 首次请求:数据被获取并存入全局缓存。

  • 后续请求:如果其他组件使用相同的 key 请求数据,库会直接从缓存中返回数据,而不会发起重复的网络请求。这极大地减少了不必要的 API 调用。

效果:应用加载更快,服务端压力更小。

状态管理简化 (Simplified State Management)

我们不再需要手动管理 isLoading, error, data。库已经帮我们封装好了。

使用 React Query 的例子:

javascript
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 <div>加载中...</div>;
  if (error) return <div>发生错误: {error.message}</div>;

  return (
    <div>
      <h1>{data.title}</h1>
      <p>{data.content}</p>
    </div>
  );
}

对比之前的 useEffect 版本,代码量大幅减少,逻辑也变得极为清晰。我们只需声明“需要什么数据 (queryKey)”和“如何获取 (queryFn)”,其余的加载、错误处理、数据存储都由库自动完成。

自动重新验证 (Automatic Revalidation)

为了解决数据陈旧问题,这些库会在特定时机自动触发数据刷新,确保界面信息与服务端保持同步。默认的触发时机包括:

  • 窗口聚焦时 (Revalidate on Focus):用户切换到其他应用再切回来,数据会自动刷新。

  • 网络重连时 (Revalidate on Reconnect):设备从离线状态恢复在线,数据会自动刷新。

  • 组件挂载时 (Revalidate on Mount):组件重新挂载时,会检查缓存数据是否过期并决定是否刷新。

  • 可配置的轮询 (Optional Interval Polling):可以设置一个时间间隔,定时刷新数据(例如,股票价格、监控仪表盘)。

效果:应用具有“实时感”,用户总能看到相对较新的数据,而开发者无需编写任何额外代码。

复杂场景的内置支持

除了上述核心功能,这些库还内置了对许多高级场景的优雅支持:

  • 分页 (Pagination) 和无限滚动 (Infinite Loading):提供了 useInfiniteQuery 等专用 Hooks,能极大地简化加载“下一页”数据的逻辑和状态管理。

  • 乐观更新 (Optimistic Updates):在修改数据的操作中(如点赞、评论),可以先假设请求会成功并立即更新 UI,然后再向服务器发送请求。如果请求失败,UI 会自动回滚到原始状态。这带来了极致流畅的交互体验。

  • 请求重试 (Request Retries):当网络请求失败时,库可以自动进行有限次数的重试。

结论

SWR 和 React Query 并非简单的数据请求工具,它们是专为现代 Web 应用设计的服务端状态管理器。它们解决的核心问题,是从手动、繁琐、易错的 useEffect 数据获取模式,转向一种自动、高效、健壮的声明式数据同步模型。

通过引入缓存、自动重新验证、状态简化等核心机制,它们帮助我们:

  • 提升开发效率:减少样板代码,专注于业务逻辑。
  • 改善用户体验:通过缓存和 Stale-While-Revalidate 策略让应用响应更快,通过自动刷新让数据更“新鲜”。
  • 增强应用健壮性:内置处理了竞态条件、错误重试、网络变化等复杂边界情况。

在今天,对于任何需要与服务端进行数据交互的非小型 React 应用而言,采用 SWR 或 React Query 已经不是一个“选项”,而是一种能够显著提升项目质量和开发体验的最佳实践。

不知道说啥了很无语了