React-Query:解锁你的应用程序潜力,轻松解决接口请求难题!
- \n }\n \n这是一个组件拉取服务端数据的简单例子,在组件中,我们简单拉取了一个接口的数据,并监听接口的状态,根据状态来更新不同的UI。\n如果有性能上的要求,那这里可能还需要加上一段缓存的逻辑\n //...\n try {\n + if(sessionUtil.get('users'){\n + updateData(sessionUtil.get('users'));\n + return;\n + }\n const data = await axios.get('/api/user');\n + sessionUtil.set('users',data)\n updateData(data);\n } catch(e) {\n setError(true);\n }\n //...\n至此,这个组件已经变得相当复杂了,如果组件拉取了好几个接口,那么这一套逻辑还得写好几遍。\n1 一些状态管理库的弊端"},{"attributes":{"header":2},"insert":"\n"},{"insert":"许多状态管理库,比如redux,可以很流畅的管理页面的状态,也有处理副作用的能力,但往往不能很好的处理服务端的状态,因为处理服务端的状态,通常还包括:\n缓存"},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"将对同一数据的多个请求消除为一个请求"},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"在后台更新“过期”数据"},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"知道数据何时“过期”"},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"尽快反映数据更新"},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"性能优化,如分页和延迟加载数据"},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"管理内存和服务器状态的垃圾收集"},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"使用结构共享记忆查询结果"},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"直到React-Query的出现,上面的问题都变得迎刃而解。\n2 React-Query"},{"attributes":{"header":2},"insert":"\n"},{"insert":"React Query 是一个开箱即用,零配置的服务端状态管理库,支持Restful和GraphQL两种类型的请求,它能帮助你很好的获取、同步、管理和缓存你的远程数据。它提供了几个简单的Hooks,借助它们可以很轻松的完成对后端数据的增删改查等操作,无需再写繁琐的数据拉取和状态判断等代码。\nReact-Query的官方文档没有大纲,阅读起来相当不方便,个人感觉,直接阅读github源码项目中的docs要更方便一些。链接地址:"},{"attributes":{"bold":true},"insert":"https://github.com/TanStack/query/tree/main/docs/react"},{"attributes":{"blockquote":true},"insert":"\n"},{"insert":"2.1 简单使用\n2.1.1 QueryClientProvider\n首先,需要在组件外层定义一个queryClient作为组件操作和使用数据的一个共同容器,通过QueryClientProvider组件注入到项目中。\n import {\n QueryClient,\n QueryClientProvider,\n useQuery,\n } from '@tanstack/react-query'\n \n const queryClient = new QueryClient()\n \n export default function App() {\n return (\n
\n
\n )\n }\nuserQuery接收一个配置对象,其中\nqueryKey:必传,用作请求数据缓存的唯一key值,也可以在数组中,写入多项如:['repoData', '1'],这样React-Query在使用的时候会自动把它拼接为/repoData/1,这个在缓存用户访问过的页面时,非常有用。"},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"queryFn:用于请求的方法,如果在QueryClient中配置了,这里可以不必再写,需要返回请求完成后所处理的数据。"},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"除了这两项基本的参数,useQuery还可以传入上面defaultOptions的所有参数,来表示对这个请求单独的配置。\n然后useQuery会返回一个对象,里面包含着请求相关的所有信息,这些信息会随着请求的进度而改变,就无须我们再使用一组state变量来进行管理了,常用的包括:\nisLoading:请求是否正在进行"},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"error:请求是否出错,这里只会对500,404等做出反应,如果有其他情况错误情况,需要在请求方法里面throw"},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"isSuccess: 请求是否成功"},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"status: 请求状态,包含上面几种情况"},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"data:返回数据"},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"除此之外,使用useQuery拉取回来的数据,会被默认缓存起来,然后可以通过配置过期时间,重新拉取等策略来进行管理。\n2.1.3 useQueryClient\n通过useQueryClient,我们可以获取到之前注入的容器实例,里面保存着所有我们缓存的信息,以及配置信息,而它本质上其实也是对React.useContext的封装。\n以上面Example组件为例,如果我们想在另一个组件访问这些数据。\n function Example() {\n const queryClient = useQueryClient()\n \n const data = queryClient.getQueryData(['repoData'])\n \n return (\n //...\n )\n }\n除了访问缓存数据,queryClient还有很多API:\n"},{"attributes":{"bold":true},"insert":"queryClient.fetchQuery"},{"attributes":{"list":"bullet"},"insert":"\n"},{"attributes":{"bold":true},"insert":"queryClient.fetchInfiniteQuery"},{"attributes":{"list":"bullet"},"insert":"\n"},{"attributes":{"bold":true},"insert":"queryClient.prefetchQuery"},{"attributes":{"list":"bullet"},"insert":"\n"},{"attributes":{"bold":true},"insert":"queryClient.prefetchInfiniteQuery"},{"attributes":{"list":"bullet"},"insert":"\n"},{"attributes":{"bold":true},"insert":"queryClient.getQueryData"},{"attributes":{"list":"bullet"},"insert":"\n"},{"attributes":{"bold":true},"insert":"queryClient.refetchQueries"},{"attributes":{"list":"bullet"},"insert":"\n"},{"attributes":{"bold":true},"insert":"queryClient.cancelQueries"},{"attributes":{"list":"bullet"},"insert":"\n"},{"attributes":{"bold":true},"insert":"queryClient.removeQueries"},{"attributes":{"list":"bullet"},"insert":"\n"},{"attributes":{"bold":true},"insert":"queryClient.resetQueries"},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"..."},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"大家可以根据需要去官网查阅。\n2.1.4 useMutation\n除了获取数据,很多时候还需要处理数据的修改,比如说最简单的todo list例子,除了拉取数据列表,还需要增删改数据,而这个时候除了需要发送接口,还需要修改本地的数据,React-Query提供了useMutation来帮我们完成这些事情。\n以上面Example组件为例,我们已经拉取到了data,现在我们想新增一条数据,那我们可以\n const {isLoading,isError,isSuccess,mutate} = useMutation({\n mutationFn: async (newData) => insertNewData(newData),\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['repoData'] });\n },\n onError: (error) => {\n console.log(error)\n }\n });\n这里我们传入了:\nmutationFn:代表元数据的方法"},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"onSuccess:接口调用成功后的回调"},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"onError: 失败的回调"},{"attributes":{"list":"bullet"},"insert":"\n"},{"insert":"返回的数据和useQuery基本是相同的,这里的mutate则是触发更改的方法,如果我们想执行useMutation中传入的方法,我们只需要调用mutate即可,传给mutate的参数都会被带到useMutation的构造方法中。\n const updateData = async (newData) => {\n mutate(newData);\n };\n\n\n以上就是React-Query最核心的对服务端数据进行增删改查的功能,除此之外,React-Query还有很多其他的能力。\n2.2 数据预获取\n有时候我们不需要整个页面loading来等待数据加载,我们更希望在用户操作之前就拉取完数据,比如用户hover详情链接,而不是点击详情的时候。\n那我们可以使用queryClient的prefetchQuery方法,提前拉取到用户可能会访问的数据,并加入到缓存中,由于不需要监听服务端状态等,所以这个方法会比useQuery高效许多。\nonMouseEnter={async () => {\n await queryClient.prefetchQuery({\n queryKey: ['character', char.id],\n queryFn: () => getCharacter(char.id),\n staleTime: 10 * 1000, // only prefetch if older than 10 seconds\n })\n}}\n2.3 分页缓存\n通过动态设置queryKey,并将keepPreviousData设置为true,我们可以很轻松的缓存之前页码的数据\n const { status, data, error, isFetching, isPreviousData } = useQuery({\n queryKey: ['projects', page],\n queryFn: () => fetchProjects(page),\n keepPreviousData: true,\n staleTime: 5000,\n })\n2.4 滚动列表渲染\n使用useInfiniteQuery定义拉取数据的方法,以及上下页的逻辑,然后会返回更新页面数据的状态,以及触发更新的方法。\n const {\n status,\n data,\n error,\n isFetching,\n isFetchingNextPage,\n isFetchingPreviousPage,\n fetchNextPage,\n fetchPreviousPage,\n hasNextPage,\n hasPreviousPage,\n } = useInfiniteQuery(\n ['projects'],\n async ({ pageParam = 0 }) => {\n const res = await axios.get('/api/projects?cursor=' + pageParam)\n return res.data\n },\n {\n getPreviousPageParam: (firstPage) => firstPage.previousId ?? undefined,\n getNextPageParam: (lastPage) => lastPage.nextId ?? undefined,\n },\n )\n2.5 devTools配套开发工具\n导入开发工具\nimport { ReactQueryDevtools } from 'react-query/devtools'\n\n{data.name}
\n{data.description}
\n 👀 {data.subscribers_count}{' '}\n ✨ {data.stargazers_count}{' '}\n 🍴 {data.forks_count}\n