作用:
缓存…(可能是编程中最难做的事情)请求的数据
将多个对相同数据的请求重复删除为一个请求
在后台更新过期数据
知道什么时候数据“过时了”
尽可能快地反映数据更新
性能优化,如分页和延迟加载数据
管理服务器状态的内存和垃圾收集
使用结构化共享记忆查询结果
1、下载
cnpm install -D react-query
注意:react-dom版本可能会报错Invalid hook call. Hooks can only be called inside of the bod...
当前代码使用版本:
"react": "^16.14.0",
"react-dom": "^16.14.0",
2、可视化管理(React Native无法管理)
import { ReactQueryDevtools } from 'react-query/devtools'
<QueryClientProvider client={queryClient}>
...其他内容
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
参数:
initialIsOpen: Boolean true将会默认打开面板
position?: "top-left" | "top-right" | "bottom-left" | "bottom-right" 按钮位置
panelProps: PropsObject 面板样式,暂不知其用法
closeButtonProps: PropsObject 关闭按钮样式,暂不知其用法
toggleButtonProps: PropsObject 开关样式,暂不知其用法
style 设置样式
className 设置样式
3、使用
import { QueryClient, QueryClientProvider, useQuery,useIsFetching } from 'react-query'
(1)创建需要请求服务状态管理的窗口组件
const queryClient = new QueryClient()
创建新的查询窗口实例,这确保数据不会在不同的用户和请求之间共享。
配置内容: 和单个查询的配置内容基本相同
{
defaultOptions: {
queries: {
refetchOnWindowFocus: false, 禁用当用户离开您的应用程序并返回陈旧的数据,React Query自动在后台为您请求新的数据
retryDelay: 方法/毫秒数字, 设置再次查询间隔时间,不要超过30秒
方法:attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000)
数字:3000
queryFn:设置查询的默认方法,即设置完成后只需要useQuery(key)或useQuery(key,{配置})即可
suspense: true, 开启支持React.Suspense
},
mutations:{ 提交的默认配置
mutationFn:设置mutation的默认方法
}
},
}
(1.5)用来连接一个QueryClient到你的应用程序:
<QueryClientProvider client={queryClient}>
组件
</QueryClientProvider>
自定义窗口组件触发React查询来重新验证,详情查看官网
如:
const queryClient = new QueryClient()
ReactDOM.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
{/* ↓ 主应用节点 */}
<App />
{/* ↓ 可视化开发工具 */}
<ReactQueryDevtools />
</QueryClientProvider>
</React.StrictMode>,
document.getElementById('root')
);
(2)在组件中使用
1、请求和获取状态query
方式一:useQuery
const res=useQuery(键名,({queryKey})=>{ queryKey为键名
键名:用于在整个应用程序中重新获取、缓存和共享查询,只要保证是唯一的
普通字符串:会被转换成数组
数组:['todo', 5, { preview: true }]
数组中的对象键值对的顺序不能决定唯一性
返回值:
一个Promise或Throws an error或结果
如:
return axios.get('/')
return (await axios.get('/movie')).data
return {msg:'ok'}
throw new Error('Oh no!')
},{
配置对象:可选,和全局配置QueryClient中配置相同
enabled:布尔值, 会在布尔值为true时,开始查询,多用于需要上一次查询结果的查询
当设置为false时
如果缓存了数据,查询将在status === 'success'或isSuccess状态下初始化
没用缓存数据,将在status === 'idle'或isIdle状态下开始
该查询将不会在挂载时自动获取。
当挂载新实例或出现新实例时,查询不会自动在后台重新获取
该查询将忽略通常会导致查询重取的查询客户机invalidateQueries和refetchQueries调用。
refetch可用于手动触发查询获取。
refetchOnWindowFocus:false 默认为true
如果用户离开您的应用程序后返回(如切换浏览器、浏览其他网页),React Query会自动在后台为您请求新的数据。
retry:3, 默认为3,查询失败时(查询函数抛出错误),自动查询的错误次数
false 禁止再次查询
true 无限制查询
(failureCount, error) => ... 允许根据请求失败的原因自定义逻辑。
retryDelay: 毫秒或方法, 设置重新查询间隔时间
方法:attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000)
数字:3000
keepPreviousData: true, 即使key已更改,当新数据未到来时,返回之前的数据状态,可用于分页器请求时使用,当新数据到达时,以前的数据被无缝交换以显示新数据。
useInfiniteQuery也能使用
placeholderData:值或方法 当请求数据未返回时,使用的默认占位值,不会持久化到缓存中
placeholderData:值
placeholderData:useMemo(() => 返回内容, [])
initialData:值或方法, 设置当数据未到来时的初始数据,会被放进缓存中,数据是全新的
initialData:值
initialData:()=>值
若通过缓存中的内容设置初始值,为保证初始值是最新的,
通过获取缓存内容的精确时间戳自动确定是否以及何时需要重新获取查询
initialData:()=>queryClient.getQueryData(key)
initialDataUpdatedAt:queryClient.getQueryState(key)?.dataUpdatedAt
通过时间戳来控制只获取多少毫秒内的缓存数据,否则让它从硬加载状态获取数据
initialData:()=>{
const state = queryClient.getQueryState('todos')
if (state && Date.now() - state.dataUpdatedAt <= 10 * 1000) {
return state.data
}
}
staleTime:设置过期时间毫秒,即在设置过期多少毫秒后才会自动重新查询数据,否则会从缓存中取isFetching也会为false
initialDataUpdatedAt:js时间戳,如Date.now(),精确确定initialData上次更新时间
catchTime:1000*60*5, 默认为5min,当查询不再有活动,设置缓存超时来删除和垃圾收集该查询
当不设置staleTime时,会使用缓存数据展示,但此时后台也会查询新数据,待获取完毕后瞬间切换为新数据。
selet:对返回数据进行改造的方法,同useInfiniteQuery的select相同,具体查看useInfiniteQuery的配置
onSuccess, 成功回调,接收参数为data
onError, 错误回调,接收参数为error
onSettled, 不管错误成功都会在之后触发的回调,第一个参数data,第二个参数error
})
返回值包含:
status:状态字符串
isLoading:该查询没有数据,目前正在获取,对应status === 'loading'
isError: 查询遇到一个错误,对应status === 'error'
isSuccess: 查询成功且数据可用,对应status === 'success'
isIdle:该查询目前是禁用的,对应status === 'idle'
isPreviousData: 是否是之前数据,当添加配置keepPreviousData:true时使用
error:如果查询处于isError状态,则可以通过error属性获取该错误。
data:如果查询处于成功状态,则可以通过data属性获取数据
refetch:用于手动再次查询,在enabled:false时,调用refetch()才会开始查询
isFetching:在任何状态下,如果查询在任何时间(包括后台重取)正在抓取,则 isFetching为真。
查询正在后台重新获取。
在任何查询抓取时(包括在后台)显示一个全局的加载指示器
const isFetching = useIsFetching()
使用示例:
const { isLoading, isError, data, error } = useQuery('todos', fetchTodoList)
if (isLoading) { 或者使用status状态字符串来进行判断if(status==='loading')
return loading状态下的组件
}
if (isError) {
return 错误状态下的组件
}
return (
正常情况下的组件
)
方式二:useQueries,动态并行查询
会返回结果集
const res=useQueries([{queryKey:x,queryFn:fn},{...}],[{和useQuery相同的配置信息},{和useQuery相同的配置信息}...])
如:
const userQueries = useQueries(
users.map(user => {
return {
queryKey: ['user', user.id],
queryFn: () => fetchUserById(user.id),
}
})
)
常用场景:
分页器请求下一页数据时,当数据未到时,展示上一页数据,而不是展示加载中状态
(1)给useQuery添加keepPreviousData:true配置
当新数据到达时,以前的数据被无缝交换以显示新数据。
(2)让组件页面展示和数据一致
从useQuery的返回值中获取isPreviousData,通过isPreviousData的布尔状态,控制如页数和内容保持一致
代码见示例
2、无限滚动useInfiniteQuery,适用于无限滚动列表等状态
const {
data,
返回数据
{
pageParams:[根据getNextPageParam/getPreviousPageParam返回的所有页码组成的数组],
第一次为undefied,因为第一次并没有调用fetch方法触发函数,所以没有返回值
pages:[当前所有内容数组]
}
hasNextPage, 是否有下一页,当getNextPageParam返回非undefined值时,为true
hasPreviousPage, 是否有上一页,getPreviousPageParam返回非undefined值,为true
isFetchingNextPage, 是否正在重新拉取下一页状态
isFetchingPreviousPage, 是否正在拉取上一页状态
fetchNextPage, 调用getNextPageParam方法的方法,若传递参数会覆盖掉pageParam
fetchPreviousPage 调用getPreviousPageParam方法的方法,若传递参数会覆盖掉pageParam
...其他返回值
} =useInfiniteQuery(key,({pageParam=0}=>{
当调用fetchNextPage,会将返回内容放进数组末尾
当调用fetchPreviousPage,会将返回内容放进数组开头
return Promsie或结果
}),{
getNextPageParam: (lastPage, pages)=>{
lastPage 当前内容
pages 当前返回的所有内容数组集合
return 结果将作为pageParma的值
},
getPreviousPageParam:(prePage,pages)=>{
prePage 当前内容
pages 当前返回的所有内容数组集合
return 结果将作为pageParam的值
},
select:(data)=>{ 对默认返回的data数据集可以进行逻辑处理,结果将替换默认返回的data,但必须保持默认默认data的属性结构,否则将被覆盖
return {
pages: [...data.pages].reverse(), 反转数据内容
pageParams: [...data.pageParams].reverse(), 反转数据页码内容
}
},
...其余配置对象
})
通过调用fetchNextPage()/fetchPreviousPage(),完成无限滚动下/上一页的数据获取
覆盖pageParam内容,fetchNextPage({ pageParam: 50 })
手动操作无线滚动的内容
queryClient.setQueryData('projects', data => ({ 如删除第一页对应的数据
pages: data.pages.slice(1),
pageParams: data.pageParams.slice(1),
}))
(3)取消查询
方式一:手动取消,会阻断isFetching等loading态,但是不能侵入axios/fetch等,取消这些需要使用方式二主动挂载后,再调用方式一
配置对象和删除查询相同
await queryClient.cancelQueries({配置对象}); 取消所有查询
await queryClient.cancelQueries(key,{配置对象});
方式二:当查询过期或不活动时自动调用
(1)与现有axios或fetch库内部的取消方法集成
(2)为请求返回的Promise对象添加.cancel方法,方法内部执行取消逻辑,并将Promise返回
import { CancelToken } from 'axios'
const query = useQuery('todos', () => {
取消axios:
const source = CancelToken.source()
const promise = axios.get('/todos', {
cancelToken: source.token,
})
promise.cancel = () => {
source.cancel('Query was cancelled by React Query')
}
return promise
取消fetch:
const controller = new AbortController()
const signal = controller.signal
const promise = fetch('/todos', {
method: 'get',
signal,
})
promise.cancel = () => controller.abort()
return promise
})
(3.5)删除查询
queryClient.removeQueries(key, {
exact:true, 是否开启精确匹配
active:ture, 匹配活跃查询
inactive:true, 匹配不活跃查询
stale:true, 匹配stale过时查询,否则匹配fresh新鲜查询
fetching:true, 匹配正在拉取的查询,否则匹配未正在拉取的查询
predicate: (query) =>布尔值, 匹配返回true的query
queryKey:设置此属性以定义要匹配的查询键
})
(3.6)手动再次拉取查询
配置对象和删除查询相同
await queryClient.refetchQueries({配置对象}) 拉取所有查询
await queryClient.refetchQueries(key,{配置对象}) 拉取匹配的查询
(3.7)其他查询方法,查看后面的react-query的queryClient属性方法这篇文章
(4)手动设置过期的查询,并重新自动进行查询,如:当提交修改后,之前查询数据变成旧的需要更新
让所有查询过期:
queryClient.invalidateQueries()
模糊匹配:
queryClient.invalidateQueries('todos') 会让使用todos开头的查询过期:
如:['todos', { type: 'done' }]
精确匹配:
queryClient.invalidateQueries('todos', { exact: true })
如:['todos']、'todos'
queryClient.invalidateQueries(['todos', { type: 'done' }])
如:['todos', { type: 'done' }]
queryClient.invalidateQueries({
predicate: query =>
query.queryKey[0] === 'todos' && query.queryKey[1]?.version >= 10,
})
如:['todos', { version: 10 }]
例:当列表状态提交更新,需要重新查询,在成功提交生命周期中设置
const mutation = useMutation(addTodo, {
onSuccess: () => {
queryClient.invalidateQueries('todos')
queryClient.invalidateQueries('reminders')
}
})
(5)预拉取
在需要数据之前预取它们,存进缓存对应的key中
await queryClient.prefetchQuery(key,()=>{
return 数据
},{
staleTime:5000, 如果数据比指定的过期时间旧,则会重新获取
})
如果数据已经同步可用,也可以直接queryClient.setQueryData,不用进行预加载
(6)获取缓存了的内容
queryClient.getQueryData(key); 读取
queryClient.setQueryData(key,值); 设置
queryClient.setQueryData(key,(data)=>{return 值}); 设置
Ejemplo de código:
páginas como los paginadores mantienen el estado de la página anterior antes de cambiar la página
import React,{
useState,useEffect,useCallback} from 'react';
import {
QueryClient, QueryClientProvider, useQuery,useIsFetching } from 'react-query'
import {
ReactQueryDevtools } from 'react-query/devtools'
import axios from 'axios';
import './query.css';
const queryClient = new QueryClient({
})
const App = () => {
return (
<QueryClientProvider client={
queryClient}>
<Example />
<ReactQueryDevtools initialIsOpen={
false} position='top-right' />
</QueryClientProvider>
)
}
function Example() {
const [page, setPage] = useState(0);
const isFetching = useIsFetching();
const {
isLoading, error, data,isPreviousData } = useQuery(['repoData'+page],async ({
queryKey}) => {
console.log('again');
console.log(isPreviousData);
return (await axios.get('/movie')).data
}, {
keepPreviousData: true,refetchOnWindowFocus:false}
)
if (isLoading) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
console.log(data)
return (
<div>
{
data}
<button
onClick={
() => {
if (!isPreviousData) {
setPage(old => old + 1) } }}
>
下一页{
page}
</button>
</div>
)
}
export default App
ejemplo de código useInfiniteQuery:
import React,{
useState,useEffect,useCallback} from 'react';
import {
QueryClient, QueryClientProvider, useQuery,useIsFetching, useInfiniteQuery } from 'react-query'
import {
ReactQueryDevtools } from 'react-query/devtools'
import axios from 'axios';
import './query.css';
const queryClient = new QueryClient({
})
const App = () => {
return (
<QueryClientProvider client={
queryClient}>
<Example />
<ReactQueryDevtools initialIsOpen={
false} position='top-right' />
</QueryClientProvider>
)
}
function Example() {
const [page, setPage] = useState(0);
const {
isLoading,error,data,fetchNextPage,fetchPreviousPage,hasNextPage,isFetchingNextPage}=useInfiniteQuery('pro',async ({
pageParam=0}) => {
return (await axios.get('/movie?page='+pageParam)).data
}, {
getNextPageParam: (lastPage, pages) => {
console.log(lastPage)
console.log(pages);
console.log(page);
return page;
},
// getPreviousPageParam: (prePage, pages) => {
// console.log(prePage);
// console.log(pages);
// return page;
// }
})
if (isLoading) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
console.log(data)
return (
<div>
{
data.pages}
<button
onClick={
() => {
setPage(old => old +1);fetchNextPage() }}
>
下一页{
page}
</button>
</div>
)
}
export default App