使用React-Query解决接口请求
前言
如果你平常会写前后端交互的react
页面,那你一定写过这样的代码.
function App() {
const [data, updateData] = useState(null);
const [isError, setError] = useState(false);
const [isLoading, setLoading] = useState(false);
useEffect(async () => {
setError(false);
setLoading(true);
try {
const data = await axios.get('/api/user');
updateData(data);
} catch(e) {
setError(true);
}
setLoading(false);
}, [])
if(isLoading) return ...
if(isError) return ...
return <List users={data}/>
}
这是一个组件拉取服务端数据的简单例子,在组件中,我们简单拉取了一个接口的数据,并监听接口的状态,根据状态来更新不同的UI。
如果有性能上的要求,那这里可能还需要加上一段缓存的逻辑
//...
try {
+ if(sessionUtil.get('users'){
+ updateData(sessionUtil.get('users'));
+ return;
+ }
const data = await axios.get('/api/user');
+ sessionUtil.set('users',data)
updateData(data);
} catch(e) {
setError(true);
}
//...
至此,这个组件已经变得相当复杂了,如果组件拉取了好几个接口,那么这一套逻辑还得写好几遍。
1 一些状态管理库的弊端
许多状态管理库,比如redux
,可以很流畅的管理页面的状态,也有处理副作用的能力,但往往不能很好的处理服务端的状态,因为处理服务端的状态,通常还包括:
- 缓存
- 将对同一数据的多个请求消除为一个请求
- 在后台更新“过期”数据
- 知道数据何时“过期”
- 尽快反映数据更新
- 性能优化,如分页和延迟加载数据
- 管理内存和服务器状态的垃圾收集
- 使用结构共享记忆查询结果
直到React-Query
的出现,上面的问题都变得迎刃而解。
2 React-Query
React Query
是一个开箱即用,零配置的服务端状态管理库,支持Restful
和GraphQL
两种类型的请求,它能帮助你很好的获取、同步、管理和缓存你的远程数据。它提供了几个简单的Hooks
,借助它们可以很轻松的完成对后端数据的增删改查等操作,无需再写繁琐的数据拉取和状态判断等代码。
React-Query
的官方文档没有大纲,阅读起来相当不方便,个人感觉,直接阅读github源码项目中的docs要更方便一些。链接地址:
https://github.com/TanStack/query/tree/main/docs/react
2.1 简单使用
2.1.1 QueryClientProvider
首先,需要在组件外层定义一个queryClient
作为组件操作和使用数据的一个共同容器,通过QueryClientProvider
组件注入到项目中。
import {
QueryClient,
QueryClientProvider,
useQuery,
} from '@tanstack/react-query'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
在创建QueryClient
的时候,我们可以传入一些参数,用于管理项目中的请求、缓存、日志的相关配置,这些配置会对整个项目生效,其中包含了四个模块的配置参数。
new QueryClient({
queryCache?: QueryCache;
mutationCache?: MutationCache;
logger?: Logger;
defaultOptions?: DefaultOptions;
})
queryCache
: 请求缓存相关配置mutationCache
: 数据修改缓存相关配置logger
: 日志相关配置defaultOptions
:请求基础配置
其中defaultOptions
用于配置项目中useQuery
请求的管理,常用的配置如下:
- staleTime: 重新获取数据的时间间隔 默认0
- cacheTime: 数据缓存时间 默认 1000 60 5 5分钟
- retry: 失败重试次数 默认 3次
- refetchOnWindowFocus: 窗口重新获得焦点时重新获取数据 默认 false
- refetchOnReconnect: 网络重新链接
- refetchOnMount: 实例重新挂载时重新拉取请求
- enabled: 如果为“false”,“useQuery”不会触发,需要使用其返回的“refetch”来触发操作
- queryFn:全局定义请求方法,其他地方使用时只需要直接传入请求参数
2.1.2 useQuery
useQuery
是React-Query
提供的用于请求接口并管理请求状态等信息的Hook。
例如:
function Example() {
const { isLoading, error, data } = useQuery({
queryKey: ['repoData'],
queryFn: () =>
fetch('https://api.github.com/repos/TanStack/query').then(
(res) => res.json(),
),
})
if (isLoading) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
return (
<div>
<h1>{data.name}</h1>
<p>{data.description}</p>
<strong>👀 {data.subscribers_count}</strong>{' '}
<strong>✨ {data.stargazers_count}</strong>{' '}
<strong>🍴 {data.forks_count}</strong>
</div>
)
}
userQuery
接收一个配置对象,其中
queryKey
:必传,用作请求数据缓存的唯一key
值,也可以在数组中,写入多项如:['repoData', '1']
,这样React-Query
在使用的时候会自动把它拼接为/repoData/1
,这个在缓存用户访问过的页面时,非常有用。queryFn
:用于请求的方法,如果在QueryClient
中配置了,这里可以不必再写,需要返回请求完成后所处理的数据。
除了这两项基本的参数,useQuery
还可以传入上面defaultOptions
的所有参数,来表示对这个请求单独的配置。
然后useQuery
会返回一个对象,里面包含着请求相关的所有信息,这些信息会随着请求的进度而改变,就无须我们再使用一组state
变量来进行管理了,常用的包括:
- isLoading:请求是否正在进行
- error:请求是否出错,这里只会对
500
,404
等做出反应,如果有其他情况错误情况,需要在请求方法里面throw
- isSuccess: 请求是否成功
- status: 请求状态,包含上面几种情况
- data:返回数据
除此之外,使用useQuery
拉取回来的数据,会被默认缓存起来,然后可以通过配置过期时间,重新拉取等策略来进行管理。
2.1.3 useQueryClient
通过useQueryClient
,我们可以获取到之前注入的容器实例,里面保存着所有我们缓存的信息,以及配置信息,而它本质上其实也是对React.useContext
的封装。
以上面Example
组件为例,如果我们想在另一个组件访问这些数据。
function Example() {
const queryClient = useQueryClient()
const data = queryClient.getQueryData(['repoData'])
return (
//...
)
}
除了访问缓存数据,queryClient
还有很多API:
- [
queryClient.fetchQuery
] - [
queryClient.fetchInfiniteQuery
] - [
queryClient.prefetchQuery
] - [
queryClient.prefetchInfiniteQuery
] - [
queryClient.getQueryData
] - [
queryClient.refetchQueries
] - [
queryClient.cancelQueries
] - [
queryClient.removeQueries
] - [
queryClient.resetQueries
]
大家可以根据需要去官网查阅。
2.1.4 useMutation
除了获取数据,很多时候还需要处理数据的修改,比如说最简单的todo list
例子,除了拉取数据列表,还需要增删改数据,而这个时候除了需要发送接口,还需要修改本地的数据,React-Query
提供了useMutation
来帮我们完成这些事情。
以上面Example
组件为例,我们已经拉取到了data,现在我们想新增一条数据,那我们可以
const {isLoading,isError,isSuccess,mutate} = useMutation({
mutationFn: async (newData) => insertNewData(newData),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['repoData'] });
},
onError: (error) => {
console.log(error)
}
});
这里我们传入了:
- mutationFn:代表元数据的方法
- onSuccess:接口调用成功后的回调
- onError: 失败的回调
返回的数据和useQuery
基本是相同的,这里的mutate
则是触发更改的方法,如果我们想执行useMutation
中传入的方法,我们只需要调用mutate
即可,传给mutate
的参数都会被带到useMutation
的构造方法中。
const updateData = async (newData) => {
mutate(newData);
};
以上就是React-Query
最核心的对服务端数据进行增删改查的功能,除此之外,React-Query
还有很多其他的能力。
2.2 数据预获取
有时候我们不需要整个页面loading来等待数据加载,我们更希望在用户操作之前就拉取完数据,比如用户hover
详情链接,而不是点击详情的时候。
那我们可以使用queryClient
的prefetchQuery
方法,提前拉取到用户可能会访问的数据,并加入到缓存中,由于不需要监听服务端状态等,所以这个方法会比useQuery
高效许多。
onMouseEnter={async () => {
await queryClient.prefetchQuery({
queryKey: ['character', char.id],
queryFn: () => getCharacter(char.id),
staleTime: 10 * 1000, // only prefetch if older than 10 seconds
})
}}
2.3 分页缓存
通过动态设置queryKey
,并将keepPreviousData
设置为true
,我们可以很轻松的缓存之前页码的数据
const { status, data, error, isFetching, isPreviousData } = useQuery({
queryKey: ['projects', page],
queryFn: () => fetchProjects(page),
keepPreviousData: true,
staleTime: 5000,
})
2.4 滚动列表渲染
使用useInfiniteQuery
定义拉取数据的方法,以及上下页的逻辑,然后会返回更新页面数据的状态,以及触发更新的方法。
const {
status,
data,
error,
isFetching,
isFetchingNextPage,
isFetchingPreviousPage,
fetchNextPage,
fetchPreviousPage,
hasNextPage,
hasPreviousPage,
} = useInfiniteQuery(
['projects'],
async ({ pageParam = 0 }) => {
const res = await axios.get('/api/projects?cursor=' + pageParam)
return res.data
},
{
getPreviousPageParam: (firstPage) => firstPage.previousId ?? undefined,
getNextPageParam: (lastPage) => lastPage.nextId ?? undefined,
},
)
2.5 devTools配套开发工具
导入开发工具
import { ReactQueryDevtools } from 'react-query/devtools'
<ReactQueryDevtools initialIsOpen={false} />
默认情况下,当process.env.NODE ENV === 'production'
时开启 Devtools ,不必担心构建时需要排除他们
浮动模式下开启,会将devtools作为固定的浮动元素安装在开发的应用程序中,并在屏幕一角提供一个切换按钮以显示和隐藏devtools
在devtools中我们可以直观的看到已经缓存下来的数据和整个项目的配置,以及各个接口的状态等。
3 总结
感谢你能看到这里,本文简单介绍了React-Query
对服务端数据进行增删改查的功能实现,以及React-Query
的一些其他能力,希望对你有用,React-Query
的使用场景没有其他状态管理库那么广泛,但还是能解决很多服务器拉取数据的痛点。