react-query是什么
react-query
适用于React Hooks
的,方便我们管理服务端请求的一个库。使用这个库你可以很方便的获取、同步、更新和缓存你的远程数据。
主要功能:
- 管理请求
- 可以实现请求、轮询、失败重试、无限加载等功能
- 可以在网络重连、窗口获得焦点等时机等向服务器发送请求同步状态
- 状态管理
- 可以把服务端的状态缓存在客户端的内存中, 从而让任意组件获取这些状态。
安装
1$ npm i react-query
2# or
3$ yarn add react-query
4复制代码
基本使用
main.jsx (项目入口文件)
1import React from 'react'
2import ReactDOM from 'react-dom/client'
3import { QueryClientProvider, QueryClient } from 'react-query'
4import { ReactQueryDevtools } from 'react-query/devtools'
5import App from './App'
6
7const queryClient = new QueryClient();
8
9ReactDOM.createRoot(document.getElementById('root')).render(
10 <QueryClientProvider client={queryClient}>
11 <App />
12 </QueryClientProvider>
13)
14复制代码
app.jsx
1import { useQuery } from 'react-query';
2import request from './request';
3
4function App(
5
6) {
7 // 查询
8 const userQuery = useQuery('users', () => request.get('/users'));
9 console.log(userQuery);
10 return (
11 (<ul>
12 {
13 userQuery?.data?.map((user) => <li key={user.id}>{user.name}</li>)
14 }
15 </ul>)
16 )
17}
18
19export default App
20复制代码
开发工具
- devtools 是React Query带有专用的可视化开发工具,可以清晰的观察每个请求的
缓存key
和其相关状态。 - 配置后, 会在页面中出现一个小图标,另外工具相关代码不会打包到生产环境中。
main.jsx
1import React from 'react'
2import ReactDOM from 'react-dom/client'
3import { QueryClientProvider, QueryClient } from 'react-query'
4import { ReactQueryDevtools } from 'react-query/devtools'
5import App from './App'
6
7const queryClient = new QueryClient();
8
9ReactDOM.createRoot(document.getElementById('root')).render(
10 <QueryClientProvider client={queryClient}>
11 <App />
12 <ReactQueryDevtools initialIsOp={true} position='bottom-right'/>
13 </QueryClientProvider>
14)
15
16复制代码
常用的两个属性:
- initialIsOp: 是否默认打开
- position: 图标显示位置
数据状态
- fetching: 请求中。
- fresh: 新鲜的。默认情况下,数据一旦被缓存就变为过时的了, 可以通过延长
staleTime
时间, 来保持数据“新鲜”。 - stale: 过时的。表示数据已经过时,需要重新通过网络请求获取新数据。
- inactive: 未激活的。当查询结果不再被使用时,该查询结果将被标记为”非活动”,并保留在缓存中,以防以后再次使用,在超过缓存时间限制后被垃圾收集。
数据为stale状态时,什么情况可能下会再次请求
- 主动调用
query.refresh
方法, 进行请求。 - 窗口重新聚焦
- 网络重新连接
- 有轮训配置
- 挂载新的查询实例,如
query
依赖的参数发生改变
查询
- 在
react-query
中, 使用useQuery
或者useInfiniteQuery
获取数据。常用的写法如下:
1useQuery(queryKey, fn, options)
2复制代码
- queryKey:当前查询的一个唯一的键值
- fn: 一个返回 Promise 的函数。
- options: 可选。配置项
配置(options)
字段 | 含义 | 说明 |
---|---|---|
staleTime | 数据过时时间,即在时间范围内,再次发起请求时,直接使用缓存数据,默认请求完成后数据直接进入过时状态 | 0 默认立刻过期 |
cacheTime | 数据缓存时间,即到时间后,数据没有被使用,则会被GC掉 | 1000 * 60 * 5 默认5分钟 |
retry | 失败重试次数 | 3 默认重试三次 |
retryDelay | 失败重试间隔时间,可以接收具体的时间,也可以接收函数,比如返回递增时间的函数 | |
refetchOnWindowFocus | 窗口重新获得焦点时重新获取数据 | true |
refetchOnReconnect | 网络重新链接时重新获取数据 | |
refetchInterval | 轮询时间 | |
enabled | 是否可用,为false不会发起请求 | |
initialData | 初始化数据,一般用于已经有完整的数据但是不确定数据是否已经变更的场景,initialData 受staleTime影响,如果初始化数据没过时,就会一直使用初始化数据, 否则重新发起请求 | |
initialStale | 初始化数据标记为是否过期 | true (默认过期) |
placeholderData | 占位数据, 类似于initialData选项,但是数据不会持久保存到缓存中, 一般是部分或不完整的数据 | |
keepPreviousData | 保留以前数据, 主要用于请求loading时,可以使用之前的数据,等新数据来时无缝切换 | |
onSuccess | 成功的回掉 | (data) => {} |
onError | 失败的回掉 | (error) => {} |
onSettled | 无论成功或者失败都会执行的回掉 | (data,error) => {} |
staleTime 和 cacheTime
2.staletime.jsx
1import { useQuery } from 'react-query';
2import request from './request';
3function App(
4
5) {
6 const { data, isLoading, isError } = useQuery('users', () => request.get('/users'), {
7 staleTime: 5000
8 })
9 if (isLoading) return <div>加载中.......</div>
10 if (isError) return <div>加载失败</div>
11 return (
12 (<ul>
13 {
14 data?.map((user) => <li key={user.id}>{user.name}</li>)
15 }
16 </ul>)
17 )
18}
19export default App;
20复制代码
3.cachetime.jsx
1import { useState } from "react";
2import { useQuery } from "react-query";
3import { ReactQueryDevtools } from "react-query/devtools";
4import request from "./request";
5function Users(
6
7) {
8 const { data, isLoading, isError, isFetching } = useQuery(
9 "users",
10 () => request.get("/users"),
11 {
12 refetchOnWindowFocus: true,
13 staleTime: Infinity, // 永不过期
14 cacheTime: 5000,
15 }
16 );
17 if (isLoading) return <div>加载中.......</div>;
18 if (isError) return <div>加载失败</div>;
19 return (
20 <>
21 <ul>
22 {data?.map((user) => (
23 <li key={user.id}>{user.name}</li>
24 ))}
25 </ul>
26 {isFetching && <div>更在更新数据...</div>}
27 </>
28 );
29}
30function App(
31
32) {
33 const [show, setShow] = useState(true);
34 return (
35 <>
36 <button onClick={() => setShow(!show)}>{show ? "隐藏" : "显示"}</button>
37 {show && <Users />}
38 <ReactQueryDevtools initialIsOpen={true} />
39 </>
40 );
41}
42export default App;
43
44复制代码
查询键(queryKey)
react-query
是基于查询键值管理查询缓存。- 查询键值可以是一个
字符串
,也可以是由许多字符串和嵌套对象组成的数组
,但是需要保证键值是可序列化的 - 查询键值序列化后, 相同的会被去重。请求依赖的参数需要放到查询键中,否则相关参数变更时,可能不会重新请求。
1// 字符串
2useQuery('todos', ...) // queryKey === ['todos']
3// 数组项的顺序影响序列化后的值
4useQuery(['todos', status, page], ...)
5// 对象的key的顺序不影响序列化后的值
6useQuery(['todos', { page, status }], ...)
7复制代码
查询键去重
4.querykey.jsx
1import { useState } from 'react';
2import { useQuery } from 'react-query';
3import { ReactQueryDevtools } from 'react-query/devtools'
4import request from './request';
5function Users({ queryKey }) {
6 const { data, isLoading, isError, isFetching } = useQuery(queryKey, () => request.get('/users'))
7 if (isLoading) return <div>加载中.......</div>
8 if (isError) return <div>加载失败</div>
9 return (
10 (
11 <>
12 <ul>
13 {
14 data?.map((user) => <li key={user.id}>{user.name}</li>)
15 }
16 </ul>
17 {isFetching && <div>更在更新数据...</div>}
18 </>
19 )
20 )
21}
22function App(
23
24) {
25 return (
26 <>
27 <Users queryKey="users" />
28 <Users queryKey="users" />
29 <ReactQueryDevtools initialIsOpen={true} />
30 </>
31 )
32}
33export default App;
34复制代码
查询函数
- 查询函数实际上可以是任何一个返回 Promise 的函数。返回的 Promise 应该返回数据或引发错误。
1// 查询函数可以解构查询键中的参数
2function Todos({ status, page }) {
3 const result = useQuery(["todos", { status, page }], fetchTodoList);
4}
5
6// 在查询函数中访问键值,状态和页面变量!
7function fetchTodoList({ queryKey }) {
8 const [_key, { status, page }] = queryKey;
9 return new Promise();
10}
11复制代码
1import { useQuery } from "react-query";
2
3useQuery({
4 queryKey: ["todo", 7],
5 queryFn: fetchTodo,
6 ...config,
7});
8复制代码
query实例
在query实例中, 包含有很多和状态有关的字段, 我们可以用这些不同的请求状态, 呈现给用户不同的展示, 对一些常用字段做下说明
字段 | 含义 | 取值 | 说明 |
---|---|---|---|
status | 状态 | loading、error、success | 你可以选择使用status === 'loading' 这种形式做请求状态的判断, 也可以直接使用isLoading 字段 |
isLoading | 是否首次加载中 | true、false | 注意首次加载请求中,不要用这个字段表示请求的loading状态,一般使用isFetching |
isFetching | 是否正在请求 | true、false | |
isError | 是否获取失败 | true、false | |
isSuccess | 是否获取成功 | true、false | |
isIdle | 是否空闲,即当前query是否发起过请求 | true、false | |
isPreviousData | 是否是老数据 | false |
方法
字段 | 含义 |
---|---|
refetch | 用于手动重新请求 |
1import { useQuery } from 'react-query';
2import request from './request';
3function App(
4
5) {
6 const { data, isLoading, isError } = useQuery('users', () => request.get('/users'))
7 if (isLoading) return <div>加载中.......</div>
8 if (isError) return <div>加载失败</div>
9 return (
10 (<ul>
11 {
12 data?.map((user) => <li key={user.id}>{user.name}</li>)
13 }
14 </ul>)
15 )
16}
17export default App;
18复制代码
全局状态处理方式
自定义hooks
- 针对多处调用的公用请求可以抽离成
hooks
,起到类似全局状态管理器的作用。
1import { useQuery } from "react-query";
2import { ReactQueryDevtools } from "react-query/devtools";
3import request from "./request";
4function useUsers(
5
6) {
7 return useQuery("users", () => request.get("/users"));
8}
9function Stats(
10
11) {
12 const { data } = useUsers();
13 return data && <h1>共计{data.length}用户</h1>;
14}
15function Users(
16
17) {
18 const { data, isLoading, isError, isFetching } = useUsers();
19 if (isLoading) return <div>加载中.......</div>;
20 if (isError) return <div>加载失败</div>;
21 return (
22 <>
23 <ul>
24 {data?.map((user) => (
25 <li key={user.id}>{user.name}</li>
26 ))}
27 </ul>
28 {isFetching && <div>更在更新数据...</div>}
29 </>
30 );
31}
32function App(
33
34) {
35 return (
36 <>
37 <Users />
38 <Stats />
39 <ReactQueryDevtools initialIsOpen={true} />
40 </>
41 );
42}
43export default App;
44复制代码
QueryObserver
- 针对不好抽离的逻辑可以采用 QueryObserver 的方式, 进行订阅处理。
- QueryObserver 可实现在任意组件中订阅状态
1import { useEffect, useState } from "react";
2import { useQuery, QueryObserver, useQueryClient } from "react-query";
3import { ReactQueryDevtools } from "react-query/devtools";
4import request from "./request";
5function Stats(
6
7) {
8 const [data, setData] = useState();
9 const queryClient = useQueryClient();
10 useEffect(() => {
11 const observer = new QueryObserver(queryClient, { queryKey: "users" });
12 const unsubscribe = observer.subscribe((result) => setData(result.data));
13 return unsubscribe;
14 }, []);
15 return data && <h1>共计{data.length}用户</h1>;
16}
17function Users(
18
19) {
20 const { data, isLoading, isError, isFetching } = useQuery("users", () =>
21 request.get("/users")
22 );
23 if (isLoading) return <div>加载中.......</div>;
24 if (isError) return <div>加载失败</div>;
25 return (
26 <>
27 <ul>
28 {data?.map((user) => (
29 <li key={user.id}>{user.name}</li>
30 ))}
31 </ul>
32 {isFetching && <div>更在更新数据...</div>}
33 </>
34 );
35}
36function App(
37
38) {
39 return (
40 <>
41 <Users />
42 <Stats />
43 <ReactQueryDevtools initialIsOpen={true} />
44 </>
45 );
46}
47export default App;
48
49复制代码
取消请求
9.cancelRequest.jsx
1import React from "react";
2import { useQuery } from "react-query";
3import { ReactQueryDevtools } from "react-query/devtools";
4import request, { CancelToken } from "./request";
5
6function User({ userId }) {
7 const { data, isLoading, isError, error, isFetching, isIdle } = useQuery(
8 ["user", userId],
9 ({ signal }) => {
10 // 旧版取消请求
11 // const source = CancelToken.source();
12 // const promise = request
13 // .get("/user", {
14 // params: { userId },
15 // cancelToken: source.token,
16 // })
17 // .catch((error) => {
18 // if (request.isCancel(error)) {
19 // console.log(error.message);
20 // }
21 // });
22 // // react-query 需要取消请求的时候, 会调用返回的promise的cancel方法
23 // promise.cancel = () => source.cancel("请求被React Query取消");
24 // return promise;
25 // 新版取消请求
26 return request.get("/user", {
27 params: { userId },
28 signal,
29 });
30 },
31 {
32 enabled: !!userId,
33 retry: 3,
34 retryDelay: 1000,
35 }
36 );
37 console.log(data);
38 if (isIdle) return null;
39 if (isLoading) return <div>加载中.......</div>;
40 if (isError) return <div>{error.message}</div>;
41 return (
42 <>
43 {data.id ? (
44 <p>
45 {data.id}:{data.name}
46 </p>
47 ) : (
48 <p>{userId}对应的用户不存在</p>
49 )}
50 {isFetching && <div>更在更新数据...</div>}
51 </>
52 );
53}
54function App(
55
56) {
57 const [userId, setUserId] = React.useState("");
58 return (
59 <>
60 <input
61 value={userId}
62 onChange={(event) => setUserId(event.target.value)}
63 />
64 <User userId={userId} />
65 <ReactQueryDevtools initialIsOpen={true} />
66 </>
67 );
68}
69export default App;
70
71复制代码
request.js
1import axios, { CancelToken } from 'axios';
2axios.interceptors.response.use(response => response.data);
3axios.defaults.baseURL = 'http://localhost:8080';
4export default axios;
5export { CancelToken }
6复制代码
并发查询
- 当存在几个请求需要并发查询时, 可以使用
useQueries
,它接收一组查询配置对象。 - 其实
useQuery
也是异步的,不会阻塞下放代码执行,但是在React.suspense
模式下就又会阻塞了, 存在一些问题
1import React from 'react';
2import { useQuery, useQueries } from 'react-query';
3import { ReactQueryDevtools } from 'react-query/devtools'
4import request from './request';
5function User(
6
7) {
8 const [usersQuery, infosQuery] = useQueries([
9 { queryKey: ['users'], queryFn: () => request.get('/users') },
10 { queryKey: ['infos'], queryFn: () => request.get(`/infos`) },
11 ]);
12 return (
13 (
14 <>
15 {usersQuery.data && <p>用户数:{usersQuery.data.length}</p>}
16 {infosQuery.data && <p>帖子数:{infosQuery.data.length}</p>}
17 </>
18 )
19 )
20}
21function App(
22
23) {
24 return (
25 <>
26 <User />
27 <ReactQueryDevtools initialIsOpen={true} />
28 </>
29 )
30}
31export default App;
32复制代码
读取查询缓存
- QueryCache是 React Query 的存储机制。它存储它包含的所有数据、元信息和查询状态
- 通常不会直接与 QueryCache 交互,而是使用QueryClient,查询缓存数据
1import React from "react";
2import { useQuery, useQueryClient } from "react-query";
3import { ReactQueryDevtools } from "react-query/devtools";
4import request from "./request";
5function Users({ setUserId }) {
6 const usersResult = useQuery("users", () => request.get("/users"), {
7 staleTime: 5000,
8 });
9 if (usersResult.isLoading) {
10 return "用户列表加载中......";
11 }
12 return (
13 <>
14 <h3>用户列表</h3>
15 <ul>
16 {usersResult.data?.map((user) => (
17 <li key={user.id} onClick={() => setUserId(user.id)}>
18 {user.name}
19 </li>
20 ))}
21 </ul>
22 </>
23 );
24}
25
26function User({ userId, setUserId }) {
27 // 获取查询客户端
28 const queryClient = useQueryClient();
29 const userResult = useQuery(
30 ["user", userId],
31 () =>
32 request.get("/user", {
33 params: { userId },
34 }),
35 {
36 staleTime: 5000,
37 initialData: () => queryClient.getQueryData("users")?.find((user) => user.id === userId),
38 // 初始化数据时在缓存中查询到自己想要的数据, 否则发起请求
39 initialStable: true,
40 }
41 );
42 if (userResult.isLoading) {
43 return "单个用户加载中......";
44 }
45 return (
46 <div>
47 <button onClick={() => setUserId(-1)}>返回</button>
48 {userResult.data && (
49 <p>
50 ID:{userResult.data.id},NAME:{userResult.data.name}
51 </p>
52 )}
53 </div>
54 );
55}
56function App(
57
58) {
59 const [userId, setUserId] = React.useState(-1);
60 return (
61 <>
62 {userId > -1 ? (
63 <User userId={userId} setUserId={setUserId} />
64 ) : (
65 <Users setUserId={setUserId} />
66 )}
67 <ReactQueryDevtools initialIsOpen={false} />
68 </>
69 );
70}
71export default App;
72
73复制代码
预缓存
- 对于某些数据, 我们在某些接口中获取到了, 可能在其他位置还需要通过接口获取,我们可以将这部分数据提前缓存到
queryClient
中,提升用户体验,减少loading或者白屏
1import React from "react";
2import { useQuery, useQueryClient } from "react-query";
3import { ReactQueryDevtools } from "react-query/devtools";
4import request from "./request";
5function Users({ setUserId }) {
6 const queryClient = useQueryClient();
7 const usersResult = useQuery(
8 "users",
9 async () => {
10 const users = await request.get("/users");
11 users.forEach((user) => {
12 // 将每个用户详情信息,缓存到queryClient
13 queryClient.setQueryData(["user", user.id], user);
14 });
15 return users;
16 },
17 {
18 staleTime: 5000,
19 }
20 );
21 if (usersResult.isLoading) {
22 return "用户列表加载中......";
23 }
24 return (
25 <>
26 <h3>用户列表</h3>
27 <ul>
28 {usersResult.data?.map((user) => (
29 <li key={user.id} onClick={() => setUserId(user.id)}>
30 {user.name}
31 </li>
32 ))}
33 </ul>
34 </>
35 );
36}
37
38function User({ userId, setUserId }) {
39 const userResult = useQuery(["user", userId], () =>
40 request.get("/user", {
41 params: { userId },
42 })
43 );
44 if (userResult.isLoading) {
45 return "单个用户加载中......";
46 }
47 return (
48 <div>
49 <button onClick={() => setUserId(-1)}>返回</button>
50 {userResult.data && (
51 <p>
52 ID:{userResult.data.id},NAME:{userResult.data.name}
53 </p>
54 )}
55 </div>
56 );
57}
58function App(
59
60) {
61 const [userId, setUserId] = React.useState(-1);
62 return (
63 <>
64 {userId > -1 ? (
65 <User userId={userId} setUserId={setUserId} />
66 ) : (
67 <Users setUserId={setUserId} />
68 )}
69 <ReactQueryDevtools initialIsOpen={false} />
70 </>
71 );
72}
73export default App;
74
75复制代码
预查询
prefetchQuery
是一种异步方法, 用法和useQuery
一样。- 通过预测用户的行为, 提前进行请求,从而达到提前呈现数据的目的。比如:鼠标划过详情按钮时就去请求详情信息、前后分页场景
1import React from "react";
2import { useQuery, useQueryClient } from "react-query";
3import { ReactQueryDevtools } from "react-query/devtools";
4import request from "./request";
5function fetchUsers(
6
7) {
8 return request.get("/users");
9}
10function Users({ setUserId }) {
11 const usersResult = useQuery("users", fetchUsers);
12 if (usersResult.isLoading) {
13 return "用户列表加载中......";
14 }
15 return (
16 <>
17 <h3>用户列表</h3>
18 <ul>
19 {usersResult.data?.map((user) => (
20 <li key={user.id} onClick={() => setUserId(user.id)}>
21 {user.name}
22 </li>
23 ))}
24 </ul>
25 </>
26 );
27}
28
29function App(
30
31) {
32 const queryClient = useQueryClient();
33 const [show, setShow] = React.useState(false);
34 return (
35 <>
36 <button
37 onClick={() => setShow(!show)}
38 onMouseOver={() =>
39 // 点那个用户有鼠标滑入按钮时,我们预测用户可能想查看用户列表, 进行预查询, 真正请求的时候, 就直接使用缓存了
40 queryClient.prefetchQuery("users", fetchUsers, { staleTime: 5000 })
41 }
42 >
43 show
44 </button>
45 {show && <Users />}
46 <ReactQueryDevtools initialIsOpen={false} />
47 </>
48 );
49}
50export default App;
51
52复制代码
分页查询
1import React from "react";
2import { useQuery, useQueryClient } from "react-query";
3import { ReactQueryDevtools } from "react-query/devtools";
4import request from "./request";
5function fetchUsers({queryKey: [_, { pageNumber }]}) {
6 return request.get("/usersByPage", {
7 params: {
8 pageNumber,
9 },
10 });
11}
12function Users(
13
14) {
15 const queryClient = useQueryClient();
16 const [pageNumber, setPageNumber] = React.useState(1);
17 const usersResult = useQuery(["users", { pageNumber }], fetchUsers, {
18 onSuccess(
19
20) {
21 queryClient.prefetchQuery(
22 ["users", { pageNumber: pageNumber + 1 }],
23 fetchUsers
24 );
25 },
26 keepPreviousData: true
27 // 1. 请求新数据时,即使查询键值已更改,上次成功获取的数据仍可用
28 // 2. 当新数据到达时,先前的数据将被无缝交换以显示新数据
29 // 3. 可以使用isPreviousData来了解当前为您提供的是什么数据
30 });
31 return (
32 <>
33 <h3>用户列表</h3>
34 <ul>
35 {usersResult.data?.list.map((user) => (
36 <li key={user.id}>{user.name}</li>
37 ))}
38 </ul>
39 {
40 <button
41 disabled={pageNumber <= 1}
42 onClick={() => setPageNumber((pageNumber) => pageNumber - 1)}
43 >
44 上一页
45 </button>
46 }
47 <span>{pageNumber}</span>
48 {
49 <button
50 disabled={usersResult.data?.pageNumber >= usersResult.data?.totalPage}
51 onClick={() => setPageNumber((pageNumber) => pageNumber + 1)}
52 >
53 下一页
54 </button>
55 }
56 </>
57 );
58}
59
60function App(
61
62) {
63 return (
64 <>
65 <Users />
66 <ReactQueryDevtools initialIsOpen={false} />
67 </>
68 );
69}
70export default App;
71
72复制代码
无限分页
1import React from "react";
2import { useQuery, useInfiniteQuery } from "react-query";
3import { ReactQueryDevtools } from "react-query/devtools";
4import request from "./request";
5function fetchUsers({ pageParam = 1 }) {
6 // 使用 useInfiniteQuery 时, 在 pageParam 获取页码
7 return request.get("/usersByPage", {
8 params: {
9 pageNumber: pageParam,
10 },
11 });
12}
13function Users(
14
15) {
16 const { data, hasNextPage, fetchNextPage } = useInfiniteQuery(
17 ["usersByPage"],
18 fetchUsers,
19 {
20 getNextPageParam: (lastPageData) => {
21 // 返回下一页的页码
22 return lastPageData.pageNumber < lastPageData.totalPage
23 ? lastPageData.pageNumber + 1
24 : false;
25 },
26 }
27 );
28 // hasNextPage 表示是否还有下一页
29 // fetchNextPage 用来请求下一页
30 return (
31 <>
32 <h3>用户列表</h3>
33 <ul>
34 {data?.pages?.map((page, index) => {
35 return (
36 <React.Fragment key={index}>
37 {page.list?.map((user) => (
38 <li key={user.id}>
39 {user.id}:{user.name}
40 </li>
41 ))}
42 </React.Fragment>
43 );
44 })}
45 </ul>
46 <button disabled={!hasNextPage} onClick={() => fetchNextPage()}>
47 加载更多
48 </button>
49 </>
50 );
51}
52
53function App(
54
55) {
56 return (
57 <>
58 <Users />
59 <ReactQueryDevtools initialIsOpen={false} />
60 </>
61 );
62}
63export default App;
64
65复制代码
变更
react-query
中使用useMutation
进行对数据的创建/更新/删除
操作useMutation
返回值字段含义同useQuery
基本使用
1import React from "react";
2import { useQuery, useQueryClient, useMutation } from "react-query";
3import { ReactQueryDevtools } from "react-query/devtools";
4import request from "./request";
5function fetchUsers(
6
7) {
8 return request.get("/users");
9}
10function Users({ setUserId }) {
11 const usersResult = useQuery("users", fetchUsers);
12 if (usersResult.isLoading) {
13 return "用户列表加载中......";
14 }
15 return (
16 <>
17 <h3>用户列表</h3>
18 <ul>
19 {usersResult.data?.map((user) => (
20 <li key={user.id} onClick={() => setUserId(user.id)}>
21 {user.name}
22 </li>
23 ))}
24 </ul>
25 </>
26 );
27}
28
29function App(
30
31) {
32 const nameRef = React.useRef();
33 const queryClient = useQueryClient();
34 const { mutate, mutateAsync, isLoading, isError, isSuccess, error, reset } = useMutation(
35 (values) => request.post("/users", values),
36 {
37 onSuccess(
38
39) {
40 // 表示重置请求状态
41 reset()
42 // 表示让users缓存失效, 并且立刻发起请求, 刷新列表更加优雅
43 // 此时表示 queryKey 以 users 开头的都会失效, 但是实际场景中也可以将粒度更加细化
44 queryClient.invalidateQueries('users');
45 },
46 onError(error) {
47 //alert(error.response.data.message);
48 },
49 onSettled(data, error) {
50 // queryClient.invalidateQueries("users");
51 },
52 }
53 );
54 const handleSubmit = (event) => {
55 event.preventDefault();
56 const name = nameRef.current.value;
57 const user = { name };
58 // mutate接收的参数会传递到useMutation的第一个会掉函数中
59 mutate(user);
60 // mutateAsync(user) 返回的是一个Promise
61 };
62 return (
63 <>
64 <Users />
65 <form onSubmit={handleSubmit}>
66 <input ref={nameRef} />
67 <input
68 type="submit"
69 value={
70 isLoading
71 ? "保存中..."
72 : isError
73 ? "保存失败"
74 : isSuccess
75 ? "保存成功"
76 : "保存"
77 }
78 />
79 </form>
80 {isError && (
81 <pre style={{ color: "red" }}>{error.response.data.message}</pre>
82 )}
83 <ReactQueryDevtools initialIsOpen={false} />
84 </>
85 );
86}
87export default App;
88
89复制代码
乐观更新和失败回滚
- 乐观更新指的是对于成功率极大的操作, 我们乐观的认为一定会成功, 我们将某部分数据提前呈现给用户, 而不是在刷库之后呈现给用户, 提高用户体验。
- 当乐观更新中真的出现了失败的情况, 我们需要失败回滚
onMutate
函数将在突变函数被触发之前触发,并传递突变函数将接收的相同变量- 如果发生变更失败,函数返回的值将传递给
onError
和onSettled
函数,并且可用于回滚乐观更新
1import React from "react";
2import { useQuery, useQueryClient, useMutation } from "react-query";
3import { ReactQueryDevtools } from "react-query/devtools";
4import request from "./request";
5function fetchUsers(
6
7) {
8 return request.get("/users");
9}
10function Users({ setUserId }) {
11 const usersResult = useQuery("users", fetchUsers);
12 if (usersResult.isLoading) {
13 return "用户列表加载中......";
14 }
15 return (
16 <>
17 <h3>用户列表</h3>
18 <ul>
19 {usersResult.data?.map((user) => (
20 <li key={user.id} onClick={() => setUserId(user.id)}>
21 {user.name}
22 </li>
23 ))}
24 </ul>
25 </>
26 );
27}
28
29function App(
30
31) {
32 const nameRef = React.useRef();
33 const queryClient = useQueryClient();
34 const {
35 mutate: saveUser,
36 isLoading,
37 isError,
38 isSuccess,
39 error,
40 reset,
41 } = useMutation((values) => request.post("/users", values), {
42 // 回滚方式一, 直接在onMutate将老状态返回, 失败后直接状态回退
43 // onMutate(values) {
44 // const oldUsers = queryClient.getQueryData("users");
45 // queryClient.setQueryData("users", (oldUsers) => [
46 // ...oldUsers,
47 // { ...values, id: String(Date.now()) },
48 // ]);
49 // return oldUsers;
50 // },
51 // onError(error, values, rollbackValues) {
52 // queryClient.setQueriesData('users', rollbackValues);
53 // },
54 // 回滚方式二, 直接在onMutate返回一个函数, 失败后直接调用
55 onMutate(values) {
56 const oldUsers = queryClient.getQueryData("users");
57 queryClient.setQueryData("users", (oldUsers) => [
58 ...oldUsers,
59 { ...values, id: String(Date.now()) },
60 ]);
61 return () => queryClient.setQueryData('users', oldUsers);
62 },
63 onError(error, values, rollback) {
64 rollback()
65 },
66 onSuccess(
67
68) {
69 reset();
70 queryClient.invalidateQueries("users");
71 },
72 onSettled(data, error) {
73 // queryClient.invalidateQueries('users');
74 },
75 });
76 const handleSubmit = (event) => {
77 event.preventDefault();
78 const name = nameRef.current.value;
79 const user = { name };
80 saveUser(user);
81 };
82 return (
83 <>
84 <Users />
85 <form onSubmit={handleSubmit}>
86 <input ref={nameRef} />
87 <input
88 type="submit"
89 value={
90 isLoading
91 ? "保存中..."
92 : isError
93 ? "保存失败"
94 : isSuccess
95 ? "保存成功"
96 : "保存"
97 }
98 />
99 </form>
100 {isError && (
101 <pre style={{ color: "red" }}>{error.response.data.message}</pre>
102 )}
103 <ReactQueryDevtools initialIsOpen={false} />
104 </>
105 );
106}
107export default App;
108
109复制代码
查询客户端
- 我们可以通过操作
查询客户端
来管理我们的缓存。
查询过滤器
1import { useQueryClient } from "react-query";
2
3const queryClient = useQueryClient();
4
5// 取消所有查询
6await queryClient.cancelQueries();
7
8// 删除所有以`posts`开头的键值的非活动查询
9queryClient.removeQueries("posts", { inactive: true });
10
11// 重新获取所有活动查询
12await queryClient.refetchQueries({ active: true });
13
14// 重新获取键中以`posts`开头的所有活动查询
15await queryClient.refetchQueries("posts", { active: true });
16复制代码
1// 获取所有正在获取的修改的数量
2await queryClient.isMutating();
3// 通过 mutationKey 过滤
4await queryClient.isMutating({ mutationKey: "post" });
5// 使用谓词函数过滤
6await queryClient.isMutating({
7 predicate: (mutation) => mutation.options.variables?.id === 1,
8});
个人笔记记录 2021 ~ 2025