react-query
@tanstack/react-query(以前叫 react-query)是一个用于 数据获取、缓存、同步、错误处理、后台刷新等功能的强大库,本质上提供了一种 基于缓存的声明式数据获取框架。
🧠 核心原理概览
React Query 的核心思想可以概括为:
用声明式的方式管理异步状态,把服务器数据变成前端 state,并自动处理缓存、请求状态、错误、刷新等复杂逻辑。
下面从架构和内部机制深入讲解
✅ 1. 架构核心组件图
text
┌──────────────────────────┐
│ QueryClient │ ← 全局管理器(缓存池、调度器)
└────────────┬─────────────┘
│
┌────▼────┐
│ Queries │ ← 每个 query 拥有一个 QueryObserver 管理订阅
└────┬────┘
│
┌──────▼────────┐
│ QueryObserver │ ← 监听一个 Query 并推送结果给 useQuery
└───────────────┘
│
┌────▼────┐
│ useQuery│ ← Hook 注册 observer 并订阅结果
└─────────┘
✅ 2. 核心机制解析
🔹 2.1 Query 缓存模型
- 每个请求都通过一个唯一的 queryKey 标识,如 ['todos', 1]
- 结果被缓存到 QueryClient 的内部内存中(可配置持久化)
- 缓存默认保持活跃 5 分钟(staleTime)
🔹 2.2 Observer 订阅机制
- 每次 useQuery 都注册一个 QueryObserver
- 多个组件使用同一个 queryKey 会共享 observer,避免重复请求
- observer 负责监听 query 的状态(loading, error, success)并触发 UI 更新
🔹 2.3 数据获取策略(Fetch Policy)
- 初次加载:拉取数据 → 缓存 → 通知 UI
- 缓存命中:可配置 staleTime,避免重复请求
- 页面聚焦或网络恢复时:自动重新获取(默认开启)
- 用户触发 refetch:立即重新拉取
🔹 2.4 后台刷新和窗口聚焦自动请求
React Query 使用浏览器事件:
- visibilitychange(页面从后台回到前台)
- online(网络恢复)
默认行为:
- 页面重新聚焦:自动 refetch
- 网络断线后恢复:自动 refetch
(可通过 refetchOnWindowFocus 和 refetchOnReconnect 配置)
✅ 3. 错误重试机制
- 默认最多重试 3 次
- 使用指数退避策略(exponential backoff)
- 用户可自定义 retry, retryDelay,甚至接入 Sentry 日志
ts
useQuery(['user'], fetchUser, {
retry: 2,
retryDelay: attemptIndex => attemptIndex * 1000
});
✅ 4. 异步状态自动处理
每个 useQuery 暴露了标准的状态字段
ts
const { data, isLoading, isError, isSuccess, error } = useQuery(...);
这些状态是由 QueryObserver 响应 Query 内部生命周期变化自动推导的,无需自己维护复杂状态。
✅ 5. 与 React 渲染解耦(关键点)
React Query 不是基于 React 状态管理实现的,而是通过 事件订阅系统 + notify batching,避免了不必要的重新渲染。
具体机制:
- 内部使用 微任务调度(microtask queue) 聚合通知(类似 React batching)
- 如果多个
observer
依赖同一个 Query,只会触发一次请求
✅ 6. QueryClient 的作用(全局管理器)
- 保存所有 Query 的缓存状态(Map 存储)
- 管理 Mutation 请求
- 提供外部访问接口,如
ts
queryClient.invalidateQueries(['user']) // 手动标记为 stale
queryClient.setQueryData(['user'], newData) // 手动更新缓存
✅ 7. Mutation 和乐观更新(MutationObserver)
- Mutation 是用于执行 POST/PUT/DELETE 等操作的 hook
- 默认不缓存结果,但可用 onSuccess 触发 invalidateQueries
- 支持乐观更新(如购物车、点赞)
ts
const mutation = useMutation(updateTodo, {
onMutate: async newTodo => {
await queryClient.cancelQueries(['todos']);
const previousTodos = queryClient.getQueryData(['todos']);
queryClient.setQueryData(['todos'], old => [...old, newTodo]);
return { previousTodos };
},
onError: (err, newTodo, context) => {
queryClient.setQueryData(['todos'], context.previousTodos);
},
onSettled: () => {
queryClient.invalidateQueries(['todos']);
}
});
✅ 总结:React Query 的底层本质
机制 | 本质 |
---|---|
缓存机制 | queryKey → Query 对象缓存于 QueryClient 中 |
状态管理 | 非 React 状态,使用 observer + 订阅推送机制 |
请求调度 | observer 控制 fetch 和状态变更,自动通知 UI |
渲染策略 | 内部 batching 和去重,最大限度减少 React 重渲染 |
错误处理 | 支持同步/异步错误捕获 + 重试 + fallback |
拓展能力 | 支持 devtools、持久化、分页、infinite scroll、SWR 模式、乐观更新等 |