React-Query全面详解

9,727 阅读12分钟

react-query是什么

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

主要功能:

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

安装

$ npm i react-query
# or
$ yarn add react-query

基本使用

main.jsx (项目入口文件)

import React from 'react'
import ReactDOM from 'react-dom/client'
import { QueryClientProvider, QueryClient } from 'react-query'
import { ReactQueryDevtools } from 'react-query/devtools'
import App from './App'

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById('root')).render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>
)

app.jsx

import { useQuery } from 'react-query';
import request from './request';

function App() {
  // 查询
  const userQuery = useQuery('users', () => request.get('/users'));
  console.log(userQuery);
  return (
    (<ul>
      {
        userQuery?.data?.map((user) => <li key={user.id}>{user.name}</li>)
      }
    </ul>)
  )
}

export default App

开发工具

  • devtools 是React Query带有专用的可视化开发工具,可以清晰的观察每个请求的缓存key和其相关状态。
  • 配置后, 会在页面中出现一个小图标,另外工具相关代码不会打包到生产环境中。

main.jsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import { QueryClientProvider, QueryClient } from 'react-query'
import { ReactQueryDevtools } from 'react-query/devtools'
import App from './App'

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById('root')).render(
  <QueryClientProvider client={queryClient}>
    <App />
    <ReactQueryDevtools initialIsOp={true} position='bottom-right'/>
  </QueryClientProvider>
)

常用的两个属性:

  • initialIsOp: 是否默认打开
  • position: 图标显示位置

数据状态

  • fetching: 请求中。
  • fresh: 新鲜的。默认情况下,数据一旦被缓存就变为过时的了, 可以通过延长staleTime时间, 来保持数据“新鲜”。
  • stale: 过时的。表示数据已经过时,需要重新通过网络请求获取新数据。
  • inactive: 未激活的。当查询结果不再被使用时,该查询结果将被标记为"非活动",并保留在缓存中,以防以后再次使用,在超过缓存时间限制后被垃圾收集。

数据为stale状态时,什么情况可能下会再次请求

  1. 主动调用query.refresh方法, 进行请求。
  2. 窗口重新聚焦
  3. 网络重新连接
  4. 有轮训配置
  5. 挂载新的查询实例,如query依赖的参数发生改变

查询

  • react-query中, 使用useQuery或者useInfiniteQuery获取数据。常用的写法如下:
useQuery(queryKey, fn, options)
  • 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

import { useQuery } from 'react-query';
import request from './request';
function App() {
    const { data, isLoading, isError } = useQuery('users', () => request.get('/users'), {
        staleTime: 5000
    })
    if (isLoading) return <div>加载中.......</div>
    if (isError) return <div>加载失败</div>
    return (
        (<ul>
        {
            data?.map((user) => <li key={user.id}>{user.name}</li>)
        }
        </ul>)
    )
}
export default App;

3.cachetime.jsx

import { useState } from "react";
import { useQuery } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import request from "./request";
function Users() {
  const { data, isLoading, isError, isFetching } = useQuery(
    "users",
    () => request.get("/users"),
    {
      refetchOnWindowFocus: true,
      staleTime: Infinity, // 永不过期
      cacheTime: 5000,
    }
  );
  if (isLoading) return <div>加载中.......</div>;
  if (isError) return <div>加载失败</div>;
  return (
    <>
      <ul>
        {data?.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
      {isFetching && <div>更在更新数据...</div>}
    </>
  );
}
function App() {
  const [show, setShow] = useState(true);
  return (
    <>
      <button onClick={() => setShow(!show)}>{show ? "隐藏" : "显示"}</button>
      {show && <Users />}
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  );
}
export default App;

查询键(queryKey)

  • react-query 是基于查询键值管理查询缓存。
  • 查询键值可以是一个字符串,也可以是由许多字符串和嵌套对象组成的数组,但是需要保证键值是可序列化的
  • 查询键值序列化后, 相同的会被去重。请求依赖的参数需要放到查询键中,否则相关参数变更时,可能不会重新请求。
// 字符串
useQuery('todos', ...) // queryKey === ['todos']
// 数组项的顺序影响序列化后的值
useQuery(['todos', status, page], ...)
// 对象的key的顺序不影响序列化后的值
useQuery(['todos', { page, status }], ...)

查询键去重

4.querykey.jsx

import { useState } from 'react';
import { useQuery } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
function Users({ queryKey }) {
  const { data, isLoading, isError, isFetching } = useQuery(queryKey, () => request.get('/users'))
  if (isLoading) return <div>加载中.......</div>
  if (isError) return <div>加载失败</div>
  return (
    (
      <>
        <ul>
          {
            data?.map((user) => <li key={user.id}>{user.name}</li>)
          }
        </ul>
        {isFetching && <div>更在更新数据...</div>}
      </>
    )
  )
}
function App() {
  return (
    <>
      <Users queryKey="users" />
      <Users queryKey="users" />
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  )
}
export default App;

查询函数

  • 查询函数实际上可以是任何一个返回 Promise 的函数。返回的 Promise 应该返回数据或引发错误。
// 查询函数可以解构查询键中的参数
function Todos({ status, page }) {
  const result = useQuery(["todos", { status, page }], fetchTodoList);
}

// 在查询函数中访问键值,状态和页面变量!
function fetchTodoList({ queryKey }) {
  const [_key, { status, page }] = queryKey;
  return new Promise();
}
import { useQuery } from "react-query";

useQuery({
  queryKey: ["todo", 7],
  queryFn: fetchTodo,
  ...config,
});

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用于手动重新请求
import { useQuery } from 'react-query';
import request from './request';
function App() {
    const { data, isLoading, isError } = useQuery('users', () => request.get('/users'))
    if (isLoading) return <div>加载中.......</div>
    if (isError) return <div>加载失败</div>
    return (
        (<ul>
        {
            data?.map((user) => <li key={user.id}>{user.name}</li>)
        }
        </ul>)
    )
}
export default App;

全局状态处理方式

自定义hooks

  • 针对多处调用的公用请求可以抽离成hooks,起到类似全局状态管理器的作用。
import { useQuery } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import request from "./request";
function useUsers() {
  return useQuery("users", () => request.get("/users"));
}
function Stats() {
  const { data } = useUsers();
  return data && <h1>共计{data.length}用户</h1>;
}
function Users() {
  const { data, isLoading, isError, isFetching } = useUsers();
  if (isLoading) return <div>加载中.......</div>;
  if (isError) return <div>加载失败</div>;
  return (
    <>
      <ul>
        {data?.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
      {isFetching && <div>更在更新数据...</div>}
    </>
  );
}
function App() {
  return (
    <>
      <Users />
      <Stats />
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  );
}
export default App;

QueryObserver

  • 针对不好抽离的逻辑可以采用 QueryObserver 的方式, 进行订阅处理。
  • QueryObserver 可实现在任意组件中订阅状态
import { useEffect, useState } from "react";
import { useQuery, QueryObserver, useQueryClient } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import request from "./request";
function Stats() {
  const [data, setData] = useState();
  const queryClient = useQueryClient();
  useEffect(() => {
    const observer = new QueryObserver(queryClient, { queryKey: "users" });
    const unsubscribe = observer.subscribe((result) => setData(result.data));
    return unsubscribe;
  }, []);
  return data && <h1>共计{data.length}用户</h1>;
}
function Users() {
  const { data, isLoading, isError, isFetching } = useQuery("users", () =>
    request.get("/users")
  );
  if (isLoading) return <div>加载中.......</div>;
  if (isError) return <div>加载失败</div>;
  return (
    <>
      <ul>
        {data?.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
      {isFetching && <div>更在更新数据...</div>}
    </>
  );
}
function App() {
  return (
    <>
      <Users />
      <Stats />
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  );
}
export default App;

取消请求

9.cancelRequest.jsx

import React from "react";
import { useQuery } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import request, { CancelToken } from "./request";

function User({ userId }) {
  const { data, isLoading, isError, error, isFetching, isIdle } = useQuery(
    ["user", userId],
    ({ signal }) => {
    // 旧版取消请求
    //   const source = CancelToken.source();
    //   const promise = request
    //     .get("/user", {
    //       params: { userId },
    //       cancelToken: source.token,
    //     })
    //     .catch((error) => {
    //       if (request.isCancel(error)) {
    //         console.log(error.message);
    //       }
    //     });
    //   // react-query 需要取消请求的时候, 会调用返回的promise的cancel方法
    //   promise.cancel = () => source.cancel("请求被React Query取消");
    //   return promise;
      // 新版取消请求
      return request.get("/user", {
        params: { userId },
        signal,
      });
    },
    {
      enabled: !!userId,
      retry: 3,
      retryDelay: 1000,
    }
  );
  console.log(data);
  if (isIdle) return null;
  if (isLoading) return <div>加载中.......</div>;
  if (isError) return <div>{error.message}</div>;
  return (
    <>
      {data.id ? (
        <p>
          {data.id}:{data.name}
        </p>
      ) : (
        <p>{userId}对应的用户不存在</p>
      )}
      {isFetching && <div>更在更新数据...</div>}
    </>
  );
}
function App() {
  const [userId, setUserId] = React.useState("");
  return (
    <>
      <input
        value={userId}
        onChange={(event) => setUserId(event.target.value)}
      />
      <User userId={userId} />
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  );
}
export default App;

request.js

import axios, { CancelToken } from 'axios';
axios.interceptors.response.use(response => response.data);
axios.defaults.baseURL = 'http://localhost:8080';
export default axios;
export { CancelToken }

并发查询

  • 当存在几个请求需要并发查询时, 可以使用useQueries,它接收一组查询配置对象。
  • 其实useQuery也是异步的,不会阻塞下放代码执行,但是在React.suspense模式下就又会阻塞了, 存在一些问题
import React from 'react';
import { useQuery, useQueries } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
function User() {
 const [usersQuery, infosQuery] = useQueries([
   { queryKey: ['users'], queryFn: () => request.get('/users') },
   { queryKey: ['infos'], queryFn: () => request.get(`/infos`) },
 ]);
  return (
    (
      <>
       {usersQuery.data && <p>用户数:{usersQuery.data.length}</p>}
       {infosQuery.data && <p>帖子数:{infosQuery.data.length}</p>}
      </>
    )
  )
}
function App() {
  return (
    <>
      <User />
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  )
}
export default App;

读取查询缓存

  • QueryCache是 React Query 的存储机制。它存储它包含的所有数据、元信息和查询状态
  • 通常不会直接与 QueryCache 交互,而是使用QueryClient,查询缓存数据
import React from "react";
import { useQuery, useQueryClient } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import request from "./request";
function Users({ setUserId }) {
  const usersResult = useQuery("users", () => request.get("/users"), {
    staleTime: 5000,
  });
  if (usersResult.isLoading) {
    return "用户列表加载中......";
  }
  return (
    <>
      <h3>用户列表</h3>
      <ul>
        {usersResult.data?.map((user) => (
          <li key={user.id} onClick={() => setUserId(user.id)}>
            {user.name}
          </li>
        ))}
      </ul>
    </>
  );
}

function User({ userId, setUserId }) {
  // 获取查询客户端
  const queryClient = useQueryClient();
  const userResult = useQuery(
    ["user", userId],
    () =>
      request.get("/user", {
        params: { userId },
      }),
    {
      staleTime: 5000,
      initialData: () => queryClient.getQueryData("users")?.find((user) => user.id === userId), 
      // 初始化数据时在缓存中查询到自己想要的数据, 否则发起请求
      initialStable: true,
    }
  );
  if (userResult.isLoading) {
    return "单个用户加载中......";
  }
  return (
    <div>
      <button onClick={() => setUserId(-1)}>返回</button>
      {userResult.data && (
        <p>
          ID:{userResult.data.id},NAME:{userResult.data.name}
        </p>
      )}
    </div>
  );
}
function App() {
  const [userId, setUserId] = React.useState(-1);
  return (
    <>
      {userId > -1 ? (
        <User userId={userId} setUserId={setUserId} />
      ) : (
        <Users setUserId={setUserId} />
      )}
      <ReactQueryDevtools initialIsOpen={false} />
    </>
  );
}
export default App;

预缓存

  • 对于某些数据, 我们在某些接口中获取到了, 可能在其他位置还需要通过接口获取,我们可以将这部分数据提前缓存到queryClient中,提升用户体验,减少loading或者白屏
import React from "react";
import { useQuery, useQueryClient } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import request from "./request";
function Users({ setUserId }) {
  const queryClient = useQueryClient();
  const usersResult = useQuery(
    "users",
    async () => {
      const users = await request.get("/users");
      users.forEach((user) => {
        // 将每个用户详情信息,缓存到queryClient
        queryClient.setQueryData(["user", user.id], user);
      });
      return users;
    },
    {
      staleTime: 5000,
    }
  );
  if (usersResult.isLoading) {
    return "用户列表加载中......";
  }
  return (
    <>
      <h3>用户列表</h3>
      <ul>
        {usersResult.data?.map((user) => (
          <li key={user.id} onClick={() => setUserId(user.id)}>
            {user.name}
          </li>
        ))}
      </ul>
    </>
  );
}

function User({ userId, setUserId }) {
  const userResult = useQuery(["user", userId], () =>
    request.get("/user", {
      params: { userId },
    })
  );
  if (userResult.isLoading) {
    return "单个用户加载中......";
  }
  return (
    <div>
      <button onClick={() => setUserId(-1)}>返回</button>
      {userResult.data && (
        <p>
          ID:{userResult.data.id},NAME:{userResult.data.name}
        </p>
      )}
    </div>
  );
}
function App() {
  const [userId, setUserId] = React.useState(-1);
  return (
    <>
      {userId > -1 ? (
        <User userId={userId} setUserId={setUserId} />
      ) : (
        <Users setUserId={setUserId} />
      )}
      <ReactQueryDevtools initialIsOpen={false} />
    </>
  );
}
export default App;

预查询

  • prefetchQuery是一种异步方法, 用法和useQuery一样。
  • 通过预测用户的行为, 提前进行请求,从而达到提前呈现数据的目的。比如:鼠标划过详情按钮时就去请求详情信息、前后分页场景
import React from "react";
import { useQuery, useQueryClient } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import request from "./request";
function fetchUsers() {
  return request.get("/users");
}
function Users({ setUserId }) {
  const usersResult = useQuery("users", fetchUsers);
  if (usersResult.isLoading) {
    return "用户列表加载中......";
  }
  return (
    <>
      <h3>用户列表</h3>
      <ul>
        {usersResult.data?.map((user) => (
          <li key={user.id} onClick={() => setUserId(user.id)}>
            {user.name}
          </li>
        ))}
      </ul>
    </>
  );
}

function App() {
  const queryClient = useQueryClient();
  const [show, setShow] = React.useState(false);
  return (
    <>
      <button
        onClick={() => setShow(!show)}
        onMouseOver={() =>
          // 点那个用户有鼠标滑入按钮时,我们预测用户可能想查看用户列表, 进行预查询, 真正请求的时候, 就直接使用缓存了 
          queryClient.prefetchQuery("users", fetchUsers, { staleTime: 5000 })
        }
      >
        show
      </button>
      {show && <Users />}
      <ReactQueryDevtools initialIsOpen={false} />
    </>
  );
}
export default App;

分页查询

import React from "react";
import { useQuery, useQueryClient } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import request from "./request";
function fetchUsers({queryKey: [_, { pageNumber }]}) {
  return request.get("/usersByPage", {
    params: {
      pageNumber,
    },
  });
}
function Users() {
  const queryClient = useQueryClient();
  const [pageNumber, setPageNumber] = React.useState(1);
  const usersResult = useQuery(["users", { pageNumber }], fetchUsers, {
    onSuccess() {
      queryClient.prefetchQuery(
        ["users", { pageNumber: pageNumber + 1 }],
        fetchUsers
      );
    },
    keepPreviousData: true
    // 1. 请求新数据时,即使查询键值已更改,上次成功获取的数据仍可用
    // 2. 当新数据到达时,先前的数据将被无缝交换以显示新数据
    // 3. 可以使用isPreviousData来了解当前为您提供的是什么数据
  });
  return (
    <>
      <h3>用户列表</h3>
      <ul>
        {usersResult.data?.list.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
      {
        <button
          disabled={pageNumber <= 1}
          onClick={() => setPageNumber((pageNumber) => pageNumber - 1)}
        >
          上一页
        </button>
      }
      <span>{pageNumber}</span>
      {
        <button
          disabled={usersResult.data?.pageNumber >= usersResult.data?.totalPage}
          onClick={() => setPageNumber((pageNumber) => pageNumber + 1)}
        >
          下一页
        </button>
      }
    </>
  );
}

function App() {
  return (
    <>
      <Users />
      <ReactQueryDevtools initialIsOpen={false} />
    </>
  );
}
export default App;

无限分页

import React from "react";
import { useQuery, useInfiniteQuery } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import request from "./request";
function fetchUsers({ pageParam = 1 }) {
  // 使用 useInfiniteQuery 时, 在 pageParam 获取页码
  return request.get("/usersByPage", {
    params: {
      pageNumber: pageParam,
    },
  });
}
function Users() {
  const { data, hasNextPage, fetchNextPage } = useInfiniteQuery(
    ["usersByPage"],
    fetchUsers,
    {
      getNextPageParam: (lastPageData) => {
        // 返回下一页的页码
        return lastPageData.pageNumber < lastPageData.totalPage
          ? lastPageData.pageNumber + 1
          : false;
      },
    }
  );
  // hasNextPage 表示是否还有下一页
  // fetchNextPage 用来请求下一页
  return (
    <>
      <h3>用户列表</h3>
      <ul>
        {data?.pages?.map((page, index) => {
          return (
            <React.Fragment key={index}>
              {page.list?.map((user) => (
                <li key={user.id}>
                  {user.id}:{user.name}
                </li>
              ))}
            </React.Fragment>
          );
        })}
      </ul>
      <button disabled={!hasNextPage} onClick={() => fetchNextPage()}>
        加载更多
      </button>
    </>
  );
}

function App() {
  return (
    <>
      <Users />
      <ReactQueryDevtools initialIsOpen={false} />
    </>
  );
}
export default App;

变更

  • react-query中使用useMutation进行对数据的创建/更新/删除操作
  • useMutation返回值字段含义同useQuery

基本使用

import React from "react";
import { useQuery, useQueryClient, useMutation } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import request from "./request";
function fetchUsers() {
  return request.get("/users");
}
function Users({ setUserId }) {
  const usersResult = useQuery("users", fetchUsers);
  if (usersResult.isLoading) {
    return "用户列表加载中......";
  }
  return (
    <>
      <h3>用户列表</h3>
      <ul>
        {usersResult.data?.map((user) => (
          <li key={user.id} onClick={() => setUserId(user.id)}>
            {user.name}
          </li>
        ))}
      </ul>
    </>
  );
}

function App() {
  const nameRef = React.useRef();
  const queryClient = useQueryClient();
  const { mutate, mutateAsync, isLoading, isError, isSuccess, error, reset } = useMutation(
    (values) => request.post("/users", values),
    {
      onSuccess() {
        // 表示重置请求状态
        reset()
        // 表示让users缓存失效, 并且立刻发起请求, 刷新列表更加优雅
        // 此时表示 queryKey 以 users 开头的都会失效, 但是实际场景中也可以将粒度更加细化
        queryClient.invalidateQueries('users');
      },
      onError(error) {
        //alert(error.response.data.message);
      },
      onSettled(data, error) {
        // queryClient.invalidateQueries("users");
      },
    }
  );
  const handleSubmit = (event) => {
    event.preventDefault();
    const name = nameRef.current.value;
    const user = { name };
    // mutate接收的参数会传递到useMutation的第一个会掉函数中
    mutate(user);
    // mutateAsync(user) 返回的是一个Promise
  };
  return (
    <>
      <Users />
      <form onSubmit={handleSubmit}>
        <input ref={nameRef} />
        <input
          type="submit"
          value={
            isLoading
              ? "保存中..."
              : isError
              ? "保存失败"
              : isSuccess
              ? "保存成功"
              : "保存"
          }
        />
      </form>
      {isError && (
        <pre style={{ color: "red" }}>{error.response.data.message}</pre>
      )}
      <ReactQueryDevtools initialIsOpen={false} />
    </>
  );
}
export default App;

乐观更新和失败回滚

  • 乐观更新指的是对于成功率极大的操作, 我们乐观的认为一定会成功, 我们将某部分数据提前呈现给用户, 而不是在刷库之后呈现给用户, 提高用户体验。
  • 当乐观更新中真的出现了失败的情况, 我们需要失败回滚
  • onMutate函数将在突变函数被触发之前触发,并传递突变函数将接收的相同变量
  • 如果发生变更失败,函数返回的值将传递给onErroronSettled函数,并且可用于回滚乐观更新
import React from "react";
import { useQuery, useQueryClient, useMutation } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import request from "./request";
function fetchUsers() {
  return request.get("/users");
}
function Users({ setUserId }) {
  const usersResult = useQuery("users", fetchUsers);
  if (usersResult.isLoading) {
    return "用户列表加载中......";
  }
  return (
    <>
      <h3>用户列表</h3>
      <ul>
        {usersResult.data?.map((user) => (
          <li key={user.id} onClick={() => setUserId(user.id)}>
            {user.name}
          </li>
        ))}
      </ul>
    </>
  );
}

function App() {
  const nameRef = React.useRef();
  const queryClient = useQueryClient();
  const {
    mutate: saveUser,
    isLoading,
    isError,
    isSuccess,
    error,
    reset,
  } = useMutation((values) => request.post("/users", values), {
    // 回滚方式一, 直接在onMutate将老状态返回, 失败后直接状态回退
    // onMutate(values) {
    //   const oldUsers = queryClient.getQueryData("users");
    //   queryClient.setQueryData("users", (oldUsers) => [
    //     ...oldUsers,
    //     { ...values, id: String(Date.now()) },
    //   ]);
    //   return oldUsers;
    // },
    // onError(error, values, rollbackValues) {
    //     queryClient.setQueriesData('users', rollbackValues);
    // },
    // 回滚方式二, 直接在onMutate返回一个函数, 失败后直接调用
    onMutate(values) {
        const oldUsers = queryClient.getQueryData("users");
        queryClient.setQueryData("users", (oldUsers) => [
          ...oldUsers,
          { ...values, id: String(Date.now()) },
        ]);
        return () => queryClient.setQueryData('users', oldUsers);
      },
      onError(error, values, rollback) {
        rollback()
      },
    onSuccess() {
      reset();
      queryClient.invalidateQueries("users");
    },
    onSettled(data, error) {
      // queryClient.invalidateQueries('users');
    },
  });
  const handleSubmit = (event) => {
    event.preventDefault();
    const name = nameRef.current.value;
    const user = { name };
    saveUser(user);
  };
  return (
    <>
      <Users />
      <form onSubmit={handleSubmit}>
        <input ref={nameRef} />
        <input
          type="submit"
          value={
            isLoading
              ? "保存中..."
              : isError
              ? "保存失败"
              : isSuccess
              ? "保存成功"
              : "保存"
          }
        />
      </form>
      {isError && (
        <pre style={{ color: "red" }}>{error.response.data.message}</pre>
      )}
      <ReactQueryDevtools initialIsOpen={false} />
    </>
  );
}
export default App;

查询客户端

  • 我们可以通过操作查询客户端来管理我们的缓存。

查询过滤器

import { useQueryClient } from "react-query";

const queryClient = useQueryClient();

// 取消所有查询
await queryClient.cancelQueries();

// 删除所有以`posts`开头的键值的非活动查询
queryClient.removeQueries("posts", { inactive: true });

// 重新获取所有活动查询
await queryClient.refetchQueries({ active: true });

// 重新获取键中以`posts`开头的所有活动查询
await queryClient.refetchQueries("posts", { active: true });
// 获取所有正在获取的修改的数量
await queryClient.isMutating();
// 通过 mutationKey 过滤
await queryClient.isMutating({ mutationKey: "post" });
// 使用谓词函数过滤
await queryClient.isMutating({
  predicate: (mutation) => mutation.options.variables?.id === 1,
});