一、两个库的基因溯源
(技术栈示例: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} />
}