react-query是什么

  • react-query 适用于React Hooks的,方便我们管理服务端请求的一个库。使用这个库你可以很方便的获取、同步、更新和缓存你的远程数据。

主要功能:

  1. 管理请求
    • 可以实现请求、轮询、失败重试、无限加载等功能
    • 可以在网络重连、窗口获得焦点等时机等向服务器发送请求同步状态
  2. 状态管理
    • 可以把服务端的状态缓存在客户端的内存中, 从而让任意组件获取这些状态。

安装

 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状态时,什么情况可能下会再次请求

  1. 主动调用query.refresh方法, 进行请求。
  2. 窗口重新聚焦
  3. 网络重新连接
  4. 有轮训配置
  5. 挂载新的查询实例,如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函数将在突变函数被触发之前触发,并传递突变函数将接收的相同变量
  • 如果发生变更失败,函数返回的值将传递给onErroronSettled函数,并且可用于回滚乐观更新
 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