在React中处理请求时我们经常会写出这样的代码
export const Component = () => {
const [isLoading, setIsLoading] = useState(true)
const [data, setData] = useState([])
const [error, setError] = useState('')
return(
<>
</>
)
}
随着代码规模增加,内部状态会变得极为混乱,维护成本会变得很高,而React-Query就是为了解决这种问题。
什么是React-Query?React Query is often described as the missing data-fetching library for React, but in more technical terms, it makes fetching, caching, synchronizing and updating server state in your React applications a breeze
如果使用React-Query,上面的代码就可以改成下面这样
export const Component = () => {
const { isLoading, data, isError, error, isFetching} = useQuery(key, func,config)
return(
<>
</>
)
}
显然,对于异步请求的状态管理会变得十分容易,除此之外,React-Query还有许多优秀的特性。
基础api
useQuery
首先使用React-Query进行一次简单的请求
function fetchSuperHeroes() {
return axios.get('http://localhost:3001/superheroes')
}
export const QueryTest = () => {
const { isLoading, data, isError, error, isFetching} = useQuery('super-heroes', fetchSuperHeroes,{
staleTime: 50000
})
return(
<>
</>
)
}
React-Query内部会建立一个缓存,缓存的key由使用者指定,这里userQuery的第一个参数就是key,返回的data就是value。useQuery是最基础的请求hook,它接受的三个参数分别是key,返回promise的请求函数,config。
useMutation
useMutation常用来发送/跟新服务端数据
//使用useMutation
const { isIdle, isLoading, isError, isSuccess, error, data} = useMutation(newTodo => {
return axios.post('/todos', newTodo)
})
特性
staleTime && cacheTime
前面提到了React-Query会依靠context建立缓存,对于每一个key-value,RQ会设置一个staleTime,在staleTime内认为该数据是fresh(defaultConfig下staleTime为0),下一次useQuery直接使用缓存数据而不会发起请求。
而cacheTime是指该缓存的有效期,超过cacheTime后数据会被清理,默认情况下cacheTime是5min
staleTime和cacheTime在使用时到底有什么区别呢?
想弄清区别必须先了解isLoading和isFetching,看一下demo
//父组件
function Father() {
const { isLoading,data, isError, error, isFetching} = useQuery('super-heroes', fetchSuperHeroes)
if(isLoading){
return<h2>Loaidng...</h2>
}
if(isFetching){
return<h2>Fetching...</h2>
}
return (
<div>
<Router>
<Link to='child'>Child</Link>
<Routes>
<Route path='/child' element={<Child />} />
</Routes>
</Router>
</div>
);
}
//子组件
export function Child() {
const { isLoading, data, isError, error, isFetching } = useQuery('super-heroes', fetchSuperHeroes)
if (isLoading) {
return <h2>Loaidng...</h2>
}
if (isFetching) {
return <h2>Fetching...</h2>
}
}
userQuery使用defaultConfig,服务端统一设置延迟2s
在首次进入Father时由于cache内没有数据,userQuery发起请求,此时isLoading为true,主页显示Loading。进入Child时,由于默认配置下data已经过期,此时ReactQuery将过期的数据先返还给使用者,然后后台发起fetch进入isFetching状态,最后用新数据更新组件状态。
如果尝试更改staleTime,进入Child时如果数据为fresh,useQuery并不会发起请求,而是直接缓存数据。
tip:在cache有效的情况下同一组件内触发rerender并不会重新请求,而是直接返回cache数据
总结一下staleTIme和cacheTime的关系
默认配置
- 默认情况下userQuery返回的数据staleTime为0,即每次返回的数据都视为过期,可以通过config设置staleTime
- 默认cacheTime是5min,5min过后数据会被清理
- 失败的query会重复3次
- 窗口重新获得focus时会重新发起请求
更多defaultConfig可以参考https://react-query.tanstack.com/guides/important-defaults
示例
窗口获得Focus时进行查询
可分别在全局config或者useQuery内进行配置
//global
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
})
function App() {
return <QueryClientProvider client={queryClient}></QueryClientProvider>
}
//single
useQuery(url, func, { refetchOnWindowFocus: false })
手动refetch
很多情况下并不需要组件mount后查询数据,而是通过交互进行查询,比如click事件
function App() {
const { isLoading,data, isError, error, isFetching, refetch} = useQuery('super-heroes', fetchSuperHeroes, {enabled: false})
if(isLoading){
return<h2>Loaidng...</h2>
}
if(isFetching){
return<h2>Fetching...</h2>
}
return (
<div>
<button onClick={() => {refetch()}}>Refetch</button>
</div>
);
}
首先设置enabled为false,这样mount时不会发起查询,在点击时调用refetch即可,第一次点击时显示loading,第二次显示fetching
定时查询
使用refetchInterval,refetchIntervalInBackground可以实现间隔一定时间查询一次, 这里指定间隔2.5s查询一次
function App() {
const { isLoading,data, isError, error, isFetching, refetch} = useQuery('super-heroes', fetchSuperHeroes, { refetchInterval: 2500,refetchIntervalInBackground: true})
if(isLoading){
return<h2>Loaidng...</h2>
}
if(isFetching){
return<h2>Fetching...</h2>
}
return (
<div>
Hello
</div>
);
}
在fetch success或error时进行回调
config内提供了onSuccess和onError设置回调函数
const { isLoading, data, isError, error, isFetching} = useQuery('super-heroes', fetchSuperHeroes,{
onSuccess:() => {console.log('success')},
onError: () => {console.log('error')}
})
手动设置数据
除了使用useQuery,还可以使用函数直接设置cache内数据
queryClient.getQueryState(queryKey)
queryClient.setQueryState(queryKey, data)
使用invalidateQueries强制使数据过期
queryClient.invalidateQueries(queryKey)
Optimistic Updates
官方文档的例子写的很好
useMutation(updateTodo, {
onMutate: async newTodo => {
// 取消已经发出的查询,防止修改本地cache
await queryClient.cancelQueries(['todos', newTodo.id])
// 缓存Optimistic Updates前的数据
const previousTodo = queryClient.getQueryData(['todos', newTodo.id])
// 发出mutation的同时直接设置本地数据
queryClient.setQueryData(['todos', newTodo.id], newTodo)
return { previousTodo, newTodo }
},
//如果mutation失败,将数据恢复到snapshot的状态
onError: (err, newTodo, context) => {
queryClient.setQueryData(
['todos', context.newTodo.id],
context.previousTodo
)
},
onSettled: newTodo => {
queryClient.invalidateQueries(['todos', newTodo.id])
},
})
总结一下Optimistic Updates流程