React中,我们可以直接使用XHR
、fetch
、axios
等进行AJAX请求。但在实际开发中,请求接口时经常有很多类似的功能需要重复编写,比如获取请求状态、分页加载、缓存等。react-query
是一个目前比较流行的请求管理库,它封装了这些功能并和React Hooks能够良好的集成到一起,因此比较推荐使用。react-query
的作者是美国程序员Tanner Linsley。
官方文档:https://tanstack.com/query/latest
Github地址:https://github.com/tanstack/query
react-query
的最新版本是v4
版本,我们执行以下命令安装:
npm install @tanstack/react-query --save
React Query提供了很多和异步请求有关的常用功能最佳实践,API也比繁杂。我们这里先不管那么多,先通过一个例子观察React Query如何配置使用,后面再讲解具体的参数和用法。下面例子中,我们使用React Query实现了一个最简单的Fetch请求管理。
index.js
import React from "react";
import ReactDOM from "react-dom/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import StudentManage from "./StudentManage";
const queryClient = new QueryClient();
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<StudentManage />
</QueryClientProvider>
</React.StrictMode>
);
StudentManage.jsx
import { useQuery } from "@tanstack/react-query";
const StudentManage = () => {
const getStudentList = async () => {
const rsp = await fetch("/api/getStudentList", {
method: "GET",
});
return await rsp.json();
};
const { isLoading, data } = useQuery({
queryKey: ["studentList"],
queryFn: getStudentList,
enabled: true,
});
return <div>{isLoading ? "Loading..." : JSON.stringify(data)}</div>;
};
export default StudentManage;
index.js
中我们创建了QueryClient
对象,并将其赋予<QueryClientProvider>
组件,这些组件提供了处理请求和缓存的功能,我们将其在根组件上配置好,子组件便能使用Hooks函数操作相关的功能。
StudentManage.jsx
中,getStudentList()
是我们自定义的异步函数,它使用fetch()
请求数据。我们使用了useQuery
对这个请求函数再次进行了封装,useQuery
提供了丰富的功能对请求进行管理,我们这里使用的isLoading
属性就能够方便的读取请求状态,配置为enabled: true
它便可以在页面加载后自动执行(enabled
默认值为true
,因此实际开发中如果需要请求自动执行不需要显示的写出,这里仅作为示例)。
JSX中,页面刷新后请求会自动执行,我们在请求过程中显示“Loading...”字样,请求完成后显示接口返回的数据。
useQuery
的参数是一个对象,其中指定了请求的配置信息。
useQuery(queryKey, queryFn, otherOptions)
下面我们详细介绍useQuery
的使用方法。
queryKey
是对应一个请求的唯一标识,React Query通过这个唯一标识判断哪个请求需要更新。queryKey
是一个数组,它可以接收多个参数,一般我们会在第1个参数中传递请求名字(字符串),后面跟着请求的参数。
const { isLoading, data } = useQuery({
queryKey: ["studentList", classId],
queryFn: async ({ queryKey }) => {
const rsp = await fetch("/api/getStudentList?classId=" + queryKey[1], {
method: "GET",
});
return await rsp.json();
},
enabled: true,
});
上面例子中,我们的queryKey
第2个参数值就是请求的GET参数,它也会传递到queryFn
中。当queryKey
中的请求参数发生变化时,请求会被重新触发(这类似于useEffect
的设计)。
queryFn
属性需要设置为一个异步函数,其内部逻辑就是调用接口,我们可以自己选用axios
、Fetch等方式请求接口,异步函数的返回值会被放入useQuery
返回值的data
属性中。
实际使用中,还会涉及到向接口传递参数问题。前面介绍过请求参数我们可以将其放在queryKey
中,useQuery
会将QueryFunctionContext
参数传递给queryFn
,我们可以在queryFn
中通过QueryFunctionContext
解构获取queryKey
。
queryFn: async ({ queryKey }) => {};
前面例子中我们已经演示如何获取请求返回的数据了,我们直接从useQuery
的返回值中解构data
属性即可。
const { isLoading, data } = useQuery({
queryKey: ["studentList"],
queryFn: getStudentList,
});
useQuery
内部使用了useState
来追踪组件的状态,并在数据改变时触发组件函数的重新执行。
React Query的请求状态分为status
和fetchStatus
两部分,它们都可以通过useQuery
的返回值解构获得。
status
取值:
loading
:加载中error
:失败success
:成功fetchStatus
取值:
paused
:请求暂停idle
:空闲fetching
:正在请求这些状态值都有对应的is
属性供我们直接判断,比如isLoading
、isError
等。之前的例子代码中,我们就通过isLoading
对当前状态是否为请求中进行了判断。在请求完成之前,data
的值都是undefined
。
注意:状态判断这里有一点比较坑,对于页面加载后自动开始的请求可以用if (isLoading) {}
,对于手动触发的请求我们则需要用if (isLoading && isFetching) {}
,因为非自动的请求isLoading
在请求触发前一直都是true
。
如果存在多个useQuery
函数,它们会并发执行。但实际开发中,我们可能遇到需要依赖请求的情况,比如先调用funcA
获取数据,再调用funcB
获取数据,没有前一步的返回数据后一步是无法执行的。React Query中我们可以通过设置enabled
属性来实现依赖请求。
const { data: dataA } = useQuery({
queryKey: ["funcA"],
queryFn: funcA,
});
const { data: dataB } = useQuery({
queryKey: ["funcB"],
queryFn: funcB,
enabled: !!dataA,
});
上面例子中,在获取到dataA
数据之前,其值为undefined
,因此funcB
的enabled
属性为false
;funcA
执行完成dataA
在useQuery
内部被赋值,同时触发组件函数的重新执行,此时dataA
有数据因此funcB
的enabled
属性变为true
,funcB
执行。