目录
[核心 Hook](#核心 Hook)
[其他实用 API](#其他实用 API)
[🔄 数据获取与查询](#🔄 数据获取与查询)
[✏️ 数据修改与提交](#✏️ 数据修改与提交)
[📊 缓存管理](#📊 缓存管理)
[🎯 查询优化技巧](#🎯 查询优化技巧)
[🔧 常用配置模式](#🔧 常用配置模式)
[三、🚀 实战示例](#三、🚀 实战示例)
[表单提交 + 缓存更新](#表单提交 + 缓存更新)
[分页 + 保持数据](#分页 + 保持数据)
[四、📝 实践总结](#四、📝 实践总结)
继上篇基础使用介绍:https://blog.csdn.net/qq_50909707/article/details/155780708?spm=1001.2014.3001.5501
本篇文章总结react query的常用api及操作。
一、常用API
React Query 主要 API 可以分为以下几类:
核心 Hook
查询相关
javascript
import { useQuery, useQueries, useInfiniteQuery } from '@tanstack/react-query'
// 基础查询
const { data, isLoading, error, refetch } = useQuery({
queryKey: ['todos'], // 查询键
queryFn: fetchTodos, // 查询函数
enabled: true, // 是否启用查询
retry: 3, // 重试次数
staleTime: 5000, // 数据保鲜时间(毫秒)
cacheTime: 60000, // 缓存时间
refetchOnWindowFocus: true, // 窗口聚焦时重新获取
refetchOnMount: true, // 组件挂载时重新获取
})
// 并行查询
const results = useQueries({
queries: [
{ queryKey: ['post', 1], queryFn: fetchPost },
{ queryKey: ['post', 2], queryFn: fetchPost }
]
})
// 无限滚动/分页查询
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
queryKey: ['projects'],
queryFn: fetchProjects,
getNextPageParam: (lastPage) => lastPage.nextCursor,
})
修改相关
javascript
import { useMutation, useQueryClient } from '@tanstack/react-query'
const queryClient = useQueryClient()
// 数据变更
const mutation = useMutation({
mutationFn: addTodo,
onSuccess: () => {
// 成功回调
queryClient.invalidateQueries({ queryKey: ['todos'] }) // 使缓存失效
},
onError: (error) => {
// 错误处理
},
retry: 2,
})
缓存管理
javascript
const queryClient = useQueryClient()
// 使查询失效并重新获取
queryClient.invalidateQueries({
queryKey: ['todos'],
exact: true // 精确匹配
})
// 使所有查询失效
queryClient.invalidateQueries()
// 获取查询数据
const data = queryClient.getQueryData(['todos', 1])
// 设置查询数据(手动更新缓存)
queryClient.setQueryData(['todos', 1], newData)
// 预取数据
await queryClient.prefetchQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
})
// 获取查询状态
const state = queryClient.getQueryState(['todos'])
// 移除查询(从缓存中删除)
queryClient.removeQueries({ queryKey: ['todos'] })
// 重置查询状态
queryClient.resetQueries({ queryKey: ['todos'] })
全局配置(QueryClient)
javascript
import { QueryClient } from '@tanstack/react-query'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1分钟
cacheTime: 5 * 60 * 1000, // 5分钟
retry: 2,
refetchOnWindowFocus: false,
},
mutations: {
retry: 1,
}
}
})
// 在App中提供
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
开发工具
javascript
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
// 在组件中使用
<ReactQueryDevtools initialIsOpen={false} />
其他实用 API
查询状态辅助
javascript
const {
data,
isLoading, // 初始加载
isFetching, // 任何获取中状态
isError, // 是否错误
error, // 错误对象
isSuccess, // 是否成功
status, // 'loading' | 'error' | 'success'
isPaused, // 网络离线时暂停
isInitialLoading, // 第一次加载
isRefetching, // 重新获取中
isStale, // 数据是否过期
failureCount, // 失败次数
} = useQuery({...})
乐观更新
javascript
useMutation({
mutationFn: updateTodo,
onMutate: async (newTodo) => {
// 取消正在进行中的查询
await queryClient.cancelQueries({ queryKey: ['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({ queryKey: ['todos'] })
}
})
常用的查询键模式
javascript
// 基本
['todos']
// 带参数
['todo', id]
// 带过滤条件
['todos', { status: 'done', page: 1 }]
// 无限查询
['projects', 'infinite']
这些是 React Query 最常用的 API,涵盖了数据获取、缓存管理、状态更新等核心功能。
二、常用操作
🔄 数据获取与查询
基础查询
javascript
// 1. 带参数的查询
const { data } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
enabled: !!userId, // 条件查询
})
// 2. 依赖查询(一个查询依赖于另一个查询的结果)
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
})
const { data: projects } = useQuery({
queryKey: ['projects', user?.id],
queryFn: () => fetchUserProjects(user.id),
enabled: !!user?.id, // 等待user加载完成
})
分页查询
javascript
// 1. 基本分页
const [page, setPage] = useState(1)
const { data } = useQuery({
queryKey: ['todos', page],
queryFn: () => fetchTodos(page),
keepPreviousData: true, // 保持上一页数据,平滑过渡
})
// 2. 无限滚动
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage
} = useInfiniteQuery({
queryKey: ['projects'],
queryFn: ({ pageParam = 1 }) => fetchProjects(pageParam),
getNextPageParam: (lastPage, pages) =>
lastPage.hasMore ? pages.length + 1 : undefined,
})
// 使用示例
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage ? '加载中...' : hasNextPage ? '加载更多' : '没有更多了'}
</button>
✏️ 数据修改与提交
基本提交
javascript
const mutation = useMutation({
mutationFn: (newTodo) => axios.post('/api/todos', newTodo),
// 提交成功后自动刷新列表
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
// 全局错误处理
onError: (error) => {
toast.error(`提交失败: ${error.message}`)
}
})
// 使用
const handleSubmit = (data) => {
mutation.mutate(data, {
// 局部配置(覆盖全局)
onSuccess: (data) => {
console.log('创建成功:', data)
navigate('/todos')
}
})
}
乐观更新(推荐👍)
javascript
const mutation = useMutation({
mutationFn: updateTodo,
// 乐观更新三部曲
onMutate: async (newTodo) => {
// 1. 取消正在进行的查询,防止冲突
await queryClient.cancelQueries({ queryKey: ['todos'] })
// 2. 保存当前数据(用于回滚)
const previousTodos = queryClient.getQueryData(['todos'])
// 3. 立即更新UI
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)
toast.error('更新失败,已恢复')
},
// 无论成功失败,重新验证数据
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
}
})
批量操作
javascript
// 并行执行多个mutation
const mutations = useMutation({
mutationFn: async (ids) => {
const results = await Promise.all(
ids.map(id => deleteItem(id))
)
return results
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['items'] })
}
})
📊 缓存管理
主动更新缓存
javascript
// 1. 添加新数据到列表
const addToCache = (newItem) => {
queryClient.setQueryData(['items'], old =>
old ? [...old, newItem] : [newItem]
)
}
// 2. 更新单条数据
const updateCacheItem = (id, updates) => {
queryClient.setQueryData(['items'], old =>
old.map(item =>
item.id === id ? { ...item, ...updates } : item
)
)
}
// 3. 删除缓存数据
const removeFromCache = (id) => {
queryClient.setQueryData(['items'], old =>
old.filter(item => item.id !== id)
)
}
预加载数据
javascript
// 1. 路由预加载
const preloadUserData = (userId) => {
queryClient.prefetchQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
staleTime: 60 * 1000, // 1分钟内有效
})
}
// 2. 鼠标悬停时预加载
const handleMouseEnter = () => {
queryClient.prefetchQuery({
queryKey: ['post', postId],
queryFn: () => fetchPost(postId),
})
}
🎯 查询优化技巧
防抖/节流查询
javascript
import { useDebounce } from 'use-debounce'
const [search, setSearch] = useState('')
const [debouncedSearch] = useDebounce(search, 500) // 防抖500ms
const { data } = useQuery({
queryKey: ['search', debouncedSearch],
queryFn: () => searchItems(debouncedSearch),
enabled: debouncedSearch.length > 2, // 至少3个字符才搜索
})
依赖查询优化
javascript
// 使用 enabled 控制查询顺序
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: fetchUser,
})
const { data: permissions } = useQuery({
queryKey: ['permissions', user?.role],
queryFn: () => fetchPermissions(user.role),
enabled: !!user?.role, // 只有user加载完成后才执行
})
const { data: settings } = useQuery({
queryKey: ['settings', permissions?.level],
queryFn: () => fetchSettings(permissions.level),
enabled: !!permissions?.level,
})
🔧 常用配置模式
全局默认配置
javascript
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1, // 失败重试1次
staleTime: 5 * 60 * 1000, // 5分钟数据保鲜
cacheTime: 10 * 60 * 1000, // 10分钟缓存
refetchOnWindowFocus: false, // 取消自动重获
refetchOnMount: 'always', // 组件挂载时刷新
refetchOnReconnect: true, // 网络重连时刷新
retryDelay: (attemptIndex) =>
Math.min(1000 * 2 ** attemptIndex, 30000), // 指数退避
}
}
})
请求取消
javascript
const fetchTodo = async ({ signal }) => {
const response = await fetch(`/api/todos/1`, {
signal, // 传入 signal
})
return response.json()
}
// React Query 会自动处理取消
const { data } = useQuery({
queryKey: ['todo', 1],
queryFn: fetchTodo,
})
// 组件卸载或查询键变化时,请求会自动取消
javascript
import axios from 'axios'
// 使用 axios 的 CancelToken
const fetchUser = async ({ queryKey, signal }) => {
const [, userId] = queryKey
const response = await axios.get(`/api/users/${userId}`, {
cancelToken: new axios.CancelToken(cancel => {
// React Query 会调用 cancel 函数
if (signal) {
signal.addEventListener('abort', () => cancel())
}
})
})
return response.data
}
三、🚀 实战示例
表单提交 + 缓存更新
javascript
function TodoForm() {
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: createTodo,
onSuccess: (newTodo) => {
// 1. 更新缓存
queryClient.setQueryData(['todos'], old => [...old, newTodo])
// 2. 可选:使后台查询失效,下次重新获取
queryClient.invalidateQueries({
queryKey: ['todos'],
exact: true
})
// 3. 重置表单
form.reset()
// 4. 显示成功消息
toast.success('创建成功!')
},
// 乐观更新(可选)
onMutate: async (newTodo) => {
await queryClient.cancelQueries(['todos'])
const previous = queryClient.getQueryData(['todos'])
queryClient.setQueryData(['todos'], old => [...old, { ...newTodo, id: Date.now() }])
return { previous }
},
onError: (err, newTodo, context) => {
queryClient.setQueryData(['todos'], context.previous)
toast.error('创建失败')
}
})
return (
<form onSubmit={(e) => {
e.preventDefault()
mutation.mutate(formData)
}}>
<input disabled={mutation.isLoading} />
<button type="submit" disabled={mutation.isLoading}>
{mutation.isLoading ? '提交中...' : '提交'}
</button>
</form>
)
}
分页 + 保持数据
javascript
function TodoList() {
const [page, setPage] = useState(1)
const { data, isFetching, isLoading } = useQuery({
queryKey: ['todos', { page }],
queryFn: () => fetchTodos(page),
keepPreviousData: true, // ✅ 关键:保持上一页数据
staleTime: 5 * 60 * 1000,
})
return (
<div>
{isLoading ? (
<div>首次加载...</div>
) : (
<>
<ul>
{data?.items?.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
{/* 分页控件 */}
<div>
<button
onClick={() => setPage(old => Math.max(old - 1, 1))}
disabled={page === 1}
>
上一页
</button>
<span>第 {page} 页</span>
<span>{isFetching && ' 更新中...'}</span>
<button
onClick={() => setPage(old => old + 1)}
disabled={!data?.hasMore}
>
下一页
</button>
</div>
</>
)}
</div>
)
}
四、📝 实践总结
-
查询键:使用数组格式,包含所有依赖项
-
条件查询 :善用
enabled控制查询时机 -
乐观更新:提升用户体验
-
缓存策略 :合理设置
staleTime和cacheTime -
错误处理:全局 + 局部错误处理结合
-
性能优化 :使用
keepPreviousData、防抖等 -
开发工具:始终开启 React Query Devtools
这些操作涵盖了 React Query 90% 的日常使用场景,掌握后可以高效处理各种数据交互需求。
总结到此!