一、两个库的基因溯源

(技术栈示例:React@18 + TypeScript)

在React生态中,数据管理工具的发展经历了三个阶段:首先是类组件时代的生命周期管理,其次是Hook模式带来的useEffect革命,最后是当前智能缓存库主导的声明式数据流时代。让我们先看SWR的典型用法:

import useSWR from 'swr'

const fetcher = (url: string) => 
  fetch(url).then(res => res.json())

function Profile() {
  // 简单到极致的API设计
  const { data, error } = useSWR('/api/user/123', fetcher, {
    revalidateOnFocus: false,
    refreshInterval: 30000
  })

  if (error) return <div>加载失败</div>
  if (!data) return <div>加载中...</div>
  return <div>你好, {data.name}</div>
}

注释解析:

  • 请求密钥('/api/user/123')既是缓存键又是请求参数
  • fetcher函数负责实际的数据获取逻辑
  • 第三个配置项实现无操作页面焦点刷新禁用
  • 定时轮询间隔设置为30秒

再看React Query的等效实现:

import { useQuery } from '@tanstack/react-query'

const fetchUser = async (userId: string) => {
  const response = await fetch(`/api/user/${userId}`)
  if (!response.ok) throw new Error('网络异常')
  return response.json()
}

function UserProfile({ userId }: { userId: string }) {
  // 明确的查询键定义体系
  const { data, error, status } = useQuery(
    ['user', userId], 
    () => fetchUser(userId),
    {
      refetchOnWindowFocus: false,
      staleTime: 5 * 60 * 1000
    }
  )

  if (status === 'loading') return <div>加载中...</div>
  if (status === 'error') return <div>{error.message}</div>
  return <div>{data.name}的资料页</div>
}

架构亮点:

  • 解耦的查询键体系(支持分层结构)
  • 独立配置的失效时间(staleTime)概念
  • 明确的状态枚举而非布尔值判断
  • 完善的TypeScript类型支持

二、核心功能逐项对比

(技术栈示例:Next.js@14项目环境)

1. 缓存控制对比实验

SWR的自动缓存清理需要借助额外配置:

// 自动清理非活动组件缓存
useSWR('/api/data', fetcher, {
  keepPreviousData: true,
  dedupingInterval: 2000
})

// 手动管理缓存案例
const { mutate } = useSWRConfig()
const refreshCache = () => mutate('/api/data')

React Query的缓存生命周期管理:

// 查询实例级缓存配置
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      cacheTime: 15 * 60 * 1000, // 内存保留时间
      staleTime: 5 * 60 * 1000   // 失效判定阈值
    }
  }
})

// 组件内精细化控制
useQuery(['project', projectId], fetchProject, {
  initialData: () => queryClient.getQueryData(['projects'])?.find(p => p.id === projectId),
  onSuccess: (data) => localStorage.setItem('latestProject', data.id)
})

2. 请求策略深度实现

SWR的自动化重试机制:

useSWR('/api/metrics', fetcher, {
  shouldRetryOnError: (error) => error.status !== 403,
  errorRetryCount: 3,
  errorRetryInterval: (attemptIndex) => 
    Math.min(1000 * 2 ** attemptIndex, 30000)
})

React Query的请求逻辑抽象:

const { refetch } = useQuery(['analysis'], fetchAnalysisData, {
  retry: (failureCount, error) => {
    if (error instanceof NoPermissionError) return false
    return failureCount < 2
  },
  retryDelay: (attempt) => attempt * 1000
})

// 主动触发场景
const handleUpdate = async () => {
  try {
    await refetch()
    showToast('数据已更新')
  } catch (err) {
    showError('刷新失败')
  }
}

三、决策矩阵与选型指南

应用场景匹配模型

评估维度 SWR优势场景 React Query优势场景
项目规模 轻量级SPA 企业级复杂应用
状态管理 独立数据流 与Redux等状态库协同
离线支持 基本缓存 强事务性离线更新
开发者体验 快速上手 深度定制需求
生态扩展 Next.js深度集成 插件化架构设计

典型异常处理对比

SWR的错误边界案例:

function UserCard() {
  const { data, error } = useSWR('/api/user', fetcher)
  
  if (error) {
    // 统一错误处理缺失
    return <ErrorUI message={error.message} />
  }
  
  // 请求中状态未明确区分
  if (!data) return <Spinner />
  
  return <Card content={data} />
}

React Query的错误管理规范:

const { data, isError, isLoading } = useQuery('todos', fetchTodos, {
  useErrorBoundary: (error) => error instanceof CriticalAPIError,
  onError: (err) => captureException(err)
})

function TodoList() {
  if (isLoading) return <Skeleton />
  if (isError) return <FallbackUI />
  
  // 请求成功后的渲染保障
  return <List items={data.items} />
}

四、工程实践启示录

混合架构融合案例

(技术栈:Next.js + tRPC + React Query)

// 封装查询客户端
const httpClient = trpc.createClient({
  url: '/api/trpc',
})

function AppWrapper() {
  return (
    <trpc.Provider client={httpClient} queryClient={queryClient}>
      <QueryClientProvider client={queryClient}>
        <MainApp />
      </QueryClientProvider>
    </trpc.Provider>
  )
}

// 业务组件集成
const { data: posts } = trpc.useQuery(['post.list'], {
  select: (data) => data.filter(post => post.isPublished),
  initialData: preloadedPosts
})

性能优化专项

React Query的分页查询优化:

function usePosts(page: number) {
  return useQuery(['posts', page], () => fetchPosts(page), {
    keepPreviousData: true,  // 保留旧数据过渡
    // 避免页码切换时的加载闪烁
    placeholderData: (previous) => previous ?? [],
  })
}

function PostPagination() {
  const [page, setPage] = useState(1)
  const { data: posts } = usePosts(page)
  
  // 预加载下一页
  useEffect(() => {
    if (page < MAX_PAGE) {
      queryClient.prefetchQuery(['posts', page + 1], 
        () => fetchPosts(page + 1))
    }
  }, [page])
  
  return <PostList posts={posts} />
}