Skip to main content

React Query

-conditionnote
status
idleisIdleenabled:false 或 mutation 未执行
loadingisLoading
errorisError,isLoadingError,isRefetchError
successisSuccess
initialData
isFetchingloading 或 后台请求
isFetched
isFetchedAfterMount避免显示缓存数据
isRefetchingisFetching && !isLoading
options
staleTimeisStale
isPlaceholderData
keepPreviousDataisPreviousData

QueryClient

  • 框架无关客户端
  • 操作数据需要 queryKey
  • 执行查询需要 queryFn
  • fetchQuery - 会返回数据
  • prefetchQuery - 不会返回数据
  • getQueryData
  • setQueryData
  • getQueriesData
  • setQueriesData
  • Queries
    • invalidate
    • refetch
    • cancel
    • remove
    • reset
  • getQueryState
  • isFetching
  • isMutating
  • getLogger
  • getDefaultOptions
  • setDefaultOptions
  • setQueryDefaults
  • getMutationDefaults
  • setMutationDefaults
  • getQueryCache
  • getMutationCache
  • clear
  • resumePausedMutations

useQuery

  • 通过 QueryObserver 监听查询
    • 监听 client + options
  • 由 useQuery 处理的选项 - 由 QueryObserver 处理
    • enabled, refetchInterval, refetchIntervalInBackground, refetchOnWindowFocus, refetchOnReconnect, notifyOnChangeProps, onSuccess, onError, onSettled, useErrorBoundary, select, suspense, keepPreviousData, placeholderData
    • notifyOnChangeProps
      • 哪些触发 rerender
      • 例如: ['data', 'error'] 在 date 和 error 变化时触发
      • 设置为 all 则所有状态变化都会 rerender
      • 默认使用 getter 跟踪使用情况
        • Object.defineProperty(trackedResult, key, {configurable: false,enumerable: true,get:()=>{/* 添加跟踪 key */}})
      • 由 QueryObserver 实现监听逻辑
// 监听数据
const observer = new QueryObserver(queryClient, { queryKey: ['posts'] });

const unsubscribe = observer.subscribe((result) => {
console.log(result);
unsubscribe();
});

Persist

Note

整体 persist 而不是单条记录维度 - #2649

  • PersistQueryClientProvider
    • 会从 persistQueryClient unsubscribe
    • 会处理 restore - persistQueryClient 返回的 promise 被 resolve
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';

const queryClient = new QueryClient({
defaultOptions: {
queries: {
cacheTime: 1000 * 60 * 60 * 24, // 24 hours
},
},
});

const persister = createSyncStoragePersister({
storage: window.localStorage,
});

ReactDOM.createRoot(rootElement).render(
<PersistQueryClientProvider client={queryClient} persistOptions={{ persister }}>
<App />
</PersistQueryClientProvider>,
);

//
const [unsubscribe, promise] = persistQueryClient({
queryClient,
persister: localStoragePersister,
});

broadcast

  • 实现基于 broadcast-channel
  • 监听 Cache 事件,广播到其他 tab

Version

React Query v5

React Query v4

React Query v3

  • 拆分 QueryCache 为 QueryClient 和更底层的 MutationCache, QueryCache
  • useQueryCache -> useQueryClient
  • QueryClientProvider 取代 ReactQueryConfigProvider 和 ReactQueryCacheProvider
  • 新增 useQueries
  • 包含 devtools - react-query/devtools
  • useQuery 参数不再是 key
    • 推荐通过 lambada 传递参数
    • 如果还是要 key useQuery(['post', id], context => fetchPost(context.queryKey[1]))
    • enabled 选项必须为 boolean - 否则会警告
    • 支持 select 部分数据 - 减少重新渲染
      • useQuery('user',fetchUser,{select:user=>user.username})
  • 废弃 usePaginatedQuery, 添加 keepPreviousData: true 实现类似功能
  • useInfiniteQuery 支持前后双向查询
  • useMutation
    • 返回对象
      • 之前 const [mutate, { status, reset }] = useMutation()
      • 现在 const { mutate, mutateAsync, status, reset } = useMutation()
        • mutate 不返回 promise, mutateAsync 返回 promise
        • mutate 支持回调 mutate('todo',{onSuccess(){},onError(){},onSettled(){}})
    • 支持 retry - 断线重联会重试,默认失败不会重试
    • 支持持久化然后恢复
  • QueryObserver, InfiniteQueryObserver, QueriesObserver - 可用于监听数据变化
  • useIsFetching - 支持 key 参数
  • v3 - 2020-12-15

React Query v2.x

import { useQuery, QueryCache, ReactQueryCacheProvider } from 'react-query';
import { ReactQueryDevtools } from 'react-query-devtools';

const queryCache = new QueryCache();

export default function App() {
return (
<>
{/* 缓存 */}
<ReactQueryCacheProvider queryCache={queryCache}>
<Example />
</ReactQueryCacheProvider>
{/* 开发工具 */}
<ReactQueryDevtools initialIsOpen />
</>
);
}
const key = '';
const {
data, // 获取到的数据或者是上次缓存的数据
error, // 错误
failureCount,
status, // 状态 - idle, loading, error, success
isError = status == 'error',
isIdle = status == 'idle',
isLoading = status == 'loading',
isSuccess = status == 'success',
isFetched, // 已经获取
isFetchedAfterMount, // 是否在 mount 后获取过
isFetching, // 是否正在获取
isStale, // 数据已失效
isPreviousData, // 使用的上次数据
isPlaceholderData, // 使用的占位数据
refetch, // 从新获取 - 除非 throwOnError 否则异常只会 log
remove, // 从缓存移除查询
} = useQuery(
key,
/* fetcher */ (...key) => fetch(key.join()).then((v) => v.json()),
/* config */ {
enabled: true, // 是否查询

// 失效控制
cacheTime: 1000 * 6 * 5, // 5m
staleTime: 0, // 失效时间 - 默认返回即失效 - 设置失效时间可减少请求 - 设置为 Infinity 不失效
// 重试控制
retry: 3, // 重试判断 - Boolean | Int | Function(failureCount, error) => shouldRetry | Boolean
retryDelay: (attempt) => Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000), // 重试延迟 - Function(retryAttempt: Int) => Int
// 刷新控制
refetchInterval: false, // 刷新间隔 - Boolean | Integer
refetchIntervalInBackground: false, // 后台刷新 - Boolean
refetchOnMount: true, // 在 mount 时刷新 - boolean | 'always'
refetchOnReconnect: true, // 网络恢复时刷新
refetchOnWindowFocus: true, // 窗口获取焦点时刷新

// 初始
initialData: undefined, // 初始数据 - any | ()=>any
initialStale: true, // 初始失效 - 如果设置了则初始数据认为是有效数据 - 会被缓存 - boolean | ()=>boolean
placeholderData: undefined, // 占位数据 - loading 的时候用 - any | ()=>any

// 状态变化回调
notifyOnStatusChange: true, // 状态变化 rerender - 设置为 false 则 data 或 error 变化时才 rerender
onSuccess: undefined, // 成功回调 - (data)=>data
onError: undefined, // 错误回调 - (error)=>void
onSettled: undefined, // 完成回调 - (data,error)=>data
suspense: false, // 启用 react suspense 模式 - loading 和 error 时会 suspense

queryFnParamsFilter: undefined, // 查询参数过滤 - args=>filteredArgs
queryKeySerializerFn: undefined,
isDataEqual: undefined, // 自定义比较操作
keepPreviousData: false, // 保留上次数据 - key 变化时
structuralSharing: true, // 结构化共享 - 跨查询缓存共享
useErrorBoundary: false,
},
);

// 分页查询
const {
data = undefined,
resolvedData, // 上次成功数据 - 忽略 key
latestData, // 本次查询数据
...queryInfo
} = usePaginatedQuery(queryKey, queryFn, options);

// 瀑布流 - queryFn 会多接受一个 fetchMoreVariable
const {
isFetchingMore, // false | 'next' | 'previous'
fetchMore, // Function(fetchMoreVariableOverride) => Promise<TResult | undefined>
canFetchMore,
...queryInfo
} = useInfiniteQuery(queryKey, queryFn, {
...queryOptions,
getFetchMore: (lastPage, allPages) => fetchMoreVariable,
});

useMutation

const [
mutate, // 修改操作
{ status, isIdle, isLoading, isSuccess, isError, data, error, reset },
] = useMutation(mutationFn, {
onMutate,
onSuccess,
onError,
onSettled,
throwOnError,
useErrorBoundary,
});

const promise = mutate(variables, {
onSuccess,
onSettled,
onError,
throwOnError,
});

FAQ

isLoading vs isFetching

  • isLoading 是 status=loading
    • 适用于第一次 loading 判断
  • isFetching 是后台正在获取
    • 即便是正在获取,isLoading 也可能为 false - 因为有上次数据
    • 适用于数据加载判断 - 刷新加载过程显示