React Query 是一个用于数据获取、缓存和同步的库,专门为 React 应用程序设计。它可以帮助开发者轻松地管理服务器状态(Server State),并提供了强大的功能,如自动请求重试、数据缓存、后台刷新、分页、乐观更新等。
React Query 的目标是简化客户端与服务器之间的数据交互,减少手动管理状态的复杂性,同时提高性能和用户体验。
1. React Query 的核心概念
在介绍如何使用之前,先了解 React Query 的一些核心概念:
1.1 Query
- Query 是 React Query 的核心功能,用于获取和缓存数据。
- 通过
useQuery
Hook 来定义一个数据获取操作。 - 每个 Query 都有一个唯一的
key
,用于标识和缓存数据。
1.2 Mutation
- Mutation 用于创建、更新或删除数据。
- 通过
useMutation
Hook 来定义一个数据变更操作。 - 它支持乐观更新和错误回滚等高级功能。
1.3 Query Client
- Query Client 是 React Query 的全局状态管理器,负责管理所有的 Query 和 Mutation。
- 通常需要在应用的根组件中配置一个
QueryClientProvider
,将 Query Client 提供给整个应用。
1.4 缓存与自动刷新
- React Query 会自动缓存查询结果,并在需要时重新获取数据(例如,窗口重新聚焦或网络重连时)。
- 它还支持后台刷新(Background Refetch),以保持数据的最新状态。
2. React Query 的安装
在项目中使用 React Query,首先需要安装它:
npm install @tanstack/react-query
或者使用 Yarn:
yarn add @tanstack/react-query
3. React Query 的基本使用
3.1 基本示例:使用 useQuery
获取数据
以下是一个简单的示例,展示如何使用 React Query 获取数据并显示在页面上:
import React from 'react';
import { useQuery } from '@tanstack/react-query';
// 模拟一个数据获取函数
const fetchTodos = async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/todos');
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
};
function TodoList() {
// 使用 useQuery Hook 获取数据
const { data, isLoading, error } = useQuery({
queryKey: ['todos'], // 唯一的 key,用于缓存数据
queryFn: fetchTodos, // 数据获取函数
});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}
export default TodoList;
关键点:
useQuery
的参数:queryKey
:查询的唯一标识符,用于缓存和重新获取数据。queryFn
:数据获取函数,必须返回一个 Promise。
- 返回值:
data
:查询返回的数据。isLoading
:查询是否正在加载。error
:如果查询失败,包含错误信息。
3.2 配置 Query Client
在 React Query 中,需要在应用的根组件中配置 QueryClientProvider
,以提供全局的 Query Client:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import App from './App';
// 创建 Query Client 实例
const queryClient = new QueryClient();
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
3.3 使用 useMutation
进行数据变更
当你需要创建、更新或删除数据时,可以使用 useMutation
:
import React from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
// 模拟一个创建数据的函数
const createTodo = async (newTodo) => {
const response = await fetch('https://jsonplaceholder.typicode.com/todos', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newTodo),
});
if (!response.ok) {
throw new Error('Failed to create todo');
}
return response.json();
};
function AddTodo() {
const queryClient = useQueryClient();
// 使用 useMutation 创建数据
const mutation = useMutation(createTodo, {
// 成功后刷新缓存
onSuccess: () => {
queryClient.invalidateQueries(['todos']); // 使 todos 的缓存失效,触发重新获取
},
});
const handleAddTodo = () => {
mutation.mutate({ title: 'New Todo', completed: false });
};
return (
<div>
<button onClick={handleAddTodo} disabled={mutation.isLoading}>
{mutation.isLoading ? 'Adding...' : 'Add Todo'}
</button>
{mutation.isError && <p>Error: {mutation.error.message}</p>}
</div>
);
}
export default AddTodo;
4. React Query 的高级功能
4.1 自动刷新数据
React Query 会在以下情况下自动刷新数据:
- 窗口重新聚焦时。
- 网络重新连接时。
- 数据过期时(可以通过
staleTime
配置过期时间)。
const { data } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
staleTime: 1000 * 60 * 5, // 数据 5 分钟内不会重新获取
});
4.2 后台刷新
可以通过 refetchInterval
配置后台刷新间隔,定期更新数据:
const { data } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
refetchInterval: 1000 * 10, // 每 10 秒刷新一次
});
4.3 乐观更新
使用 useMutation
时,可以配置乐观更新,提升用户体验:
const mutation = useMutation(updateTodo, {
onMutate: async (newTodo) => {
// 在 mutation 开始之前,手动更新缓存
await queryClient.cancelQueries(['todos']);
const previousTodos = queryClient.getQueryData(['todos']);
queryClient.setQueryData(['todos'], (old) =>
old.map((todo) =>
todo.id === newTodo.id ? { ...todo, ...newTodo } : todo
)
);
return { previousTodos };
},
onError: (err, newTodo, context) => {
// 如果出错,回滚到之前的状态
queryClient.setQueryData(['todos'], context.previousTodos);
},
onSettled: () => {
// 成功或失败后重新获取数据
queryClient.invalidateQueries(['todos']);
},
});
5. React Query 的优点
- 数据缓存:自动缓存查询结果,避免重复请求。
- 自动重试:请求失败时会自动重试。
- 后台刷新:支持后台刷新数据,保持数据最新。
- 开发体验:良好的 TypeScript 支持和强大的 DevTools。
- 与 React 深度集成:完全基于 React Hook,符合 React 的使用习惯。
6. React Query 与 Redux 的区别
特性 | React Query | Redux |
---|---|---|
用途 | 管理服务器状态(Server State)。 | 管理客户端状态(Client State)。 |
状态存储 | 数据存储在内存中(由 React Query 管理)。 | 状态存储在 Redux 的 store 中。 |
异步操作 | 内置支持(useQuery 和 useMutation )。 | 需要中间件(如 redux-thunk 或 redux-saga )。 |
学习曲线 | 简单,专注于数据获取和缓存。 | 较复杂,需要手动管理状态和异步逻辑。 |
适用场景 | 服务器数据(API 请求、缓存、同步)。 | 客户端状态(表单、UI 状态等)。 |
7. 总结
React Query 是一个功能强大的工具,专注于服务器状态管理。如果你的项目中大量依赖 API 请求和数据同步,React Query 是一个非常好的选择。与 Redux 不同,它不适合管理客户端状态(如表单输入、UI 状态等)。
-
什么时候使用 React Query?
- 项目中需要频繁与服务器交互。
- 需要缓存和自动同步服务器数据。
- 希望简化数据获取逻辑。
-
什么时候使用 Redux?
- 需要全局管理客户端状态。
- 状态逻辑复杂(如跨组件共享和嵌套状态)。
React Query 和 Redux 并不是互相替代的工具,而是可以互补使用的技术栈。