当前位置: 首页 > 工具软件 > React-Total > 使用案例 >

React-Query学习及实践总结

闽焕
2023-12-01


前言

最近团队中用了react-query,了解了一下之后确实挺好玩的,简单记录一下


一、react-query能干嘛?

在前端平时的开发中,可以把我们需要维护的状态分为两类:

  1. 用户交互的中间状态。如组件的状态,isLoading, isOpen
  2. 服务端状态。通过请求得到并在前端维护

react-query能够更好的帮助我们处理第二类状态。服务端状态通常会存在组件中,如果要复用,要存在全局状态如redux中。但这样有两个问题:

  1. 需要重复处理请求中间状态,比如loading,error的判断,每次都要写
  2. 不同于交互的中间状态,服务端状态更应被归类为「缓存」,对于不同组件要共享的缓存,还要考虑「缓存」的失效和更新

而react-query可以无痛的完全替我们处理这些问题:

  1. 在hooks内部封装loading,error等方便直接使用
  2. 多个组件请求同一个query时只发出一个请求
  3. 缓存数据失效/更新策略(判断缓存合适失效,失效后自动请求数据)

二、常见用例

1.引入库

代码如下(示例):

import { useQuery } from 'react-query';

2.请求useQuery

代码如下(示例):

  // 通过useQuery请求数据,hook返回的data和isLoding都可以直接使用
  // 除了data,isLoading,还有其他返回如 status,error,isFetching等等
  const { data, isLoading } = useQuery(
  	// useQuery的第一个参数:查询的唯一key,要求必须是可序列化的
    ['statData', { date, group }],
    // useQuery的第二个参数:查询函数,要求必须是返回Promise的函数
    // 在查询函数中,也可以提取到key中的参数,比如
    // const [_key, { date, department }] = queryKey;
    async () => {
      const { data: dataObj } = await fetchStatData({
        year: date.getFullYear(),
        month: date.getMonth(),
        deptId: group ? group.id : '',
      });
      return dataObj;
    },
    // useQuery的第三个参数:配置对象config
    {
      // 当团队id存在时才能发起请求
      enabled: !!(group && group.id),
    }
  );

最佳实践:

  1. 将查询依赖的变量参数,写在查询的key中,本例中是 date和department
  2. key中的依赖参数,使用['statData', { date, department }]而非['statData', date, department]。使用对象,不管对象中键值的顺序如何,都会被认为是相等的,react-query才能准确的复用他们
  3. 为了使 React Query 确定查询错误,查询函数的错误必须抛出,throw new Error('Oh no')

2.列表useInfiniteQuery

代码如下(示例):

  // 列表数据
  const {
    data, // 数据
    fetchNextPage, // 请求下一页的方法
    hasNextPage, // 是否还有下一页
    isLoading, // loading
  } = useInfiniteQuery(
  	// 参数1:query key
    ['statisticsByMonthList', { date, group }],
    // 参数2:query function
    async ({ pageNum = 1 }) => {
      const { data: dataObj } = await fetchList({
        year: date.getFullYear(),
        month: date.getMonth(),
        groupId: group ? group.id : '',
        pageNum,
        pageSize: 10,
      });
      // 数据处理并返回
      // 下边的getNextPageParam的入参lastPage就是这个
      return {
        list: dataObj?.list ?? [],
        pageNum, // 记录当前页码
        total: dataObj?.total,
        hasNextPage: dataObj?.hasNextPage, // 计算出是否有下一页
      };
    },
    // 配置
    {
      // 团队id存在才能请求
      enabled: !!(group && group.id),
      // 获取fetchNextPage函数的入参,因为query function只有一个入参pageNum
      // 所以这里返回下一页的页码即可
      getNextPageParam: (lastPage) => {
        if (lastPage?.hasNextPage) {
          return lastPage.pageNum + 1; // 计算下一页页码
        }
      },
    }
  );

注意理解:

  1. query function的返回作为getNextPageParam的入参
  2. getNextPageParam的返回作为fetchNextPage的入参
  3. fetchNextPage实际上调用的也是query function,所以query function和getNextPageParam的返回,互为对方的入参

3. 并行查询

  1. 手动并行查询,只要连续使用任意数量的useQueryuseInfiniteQuery即可
  2. 使用useQueries,入参是一个function,它返回一组useQuery的查询对象{ queryKey, queryFn,queryConfig }
  3. 当并行查询有依赖时,比如a获取用户,b通过用户id获取用户订单,使用config 中的enable,即可告诉b,当a获取结果,得到id之后再运行

4. 状态指示器

  1. isFetching 表示该query正在后台重新请求
  2. 另外还有const isFetching = useIsFetching();钩子,作为全局加载指示器

5. 禁用/暂停查询

query config 中的enable 为 false 时:

  1. 如果该query已经缓存了数据,则isSuccess
  2. 若该query没有缓存的数据,则isIdle
  3. 不会自动获取数据
  4. 手动 refetch可以触发query

6. 分页/滞后查询 keepPreviousData

平时的分页,默认展示第一页,翻第二页会请求一次,这时翻第一页又会请求一次,因为每个新页面都被视为一个全新的查询。

对于分页查询,让uerQuery的config,增加配置:keepPreviousData: true,,此时:

  1. 翻第二页,第一页的数据仍然可用
  2. 翻页时会显示新数据
  3. 可以使用isPreviousData来了解当前是什么数据

7. 查询数据占位符 placeholder-query-data

比如请求一个博客列表,当新用户的列表为空,写一个placeholderData,能够在列表为空时,展示一个示例的博客的mock数据

8. 修改 mutations

使用mutations去修改状态,注意,因为 mutation.mutate是异步方法, 在React16及之前的版本,由于React事件池的限制,使用时要用同步方法包裹,不能直接放在 onSubmit上

9. 查询失效 query-invalidation

之前说了,query获取的数据会被缓存,但缓存什么时候失效呢?我们可以通过:

// 使缓存中的每个查询都无效
queryClient.invalidateQueries();
// 无效每个查询键值以 `todos` 开头的查询
queryClient.invalidateQueries("todos");

手动的让缓存失效,失效后该query会被标记为过时,再次使用时该query会在后台重新获取数据

10. 修改导致的失效invalidation-from-mutations

例如:在增加todo的mutation的回调中让列表失效,这样,addTodo之后,就会重新获取列表数据

import { useMutation, useQueryClient } from "react-query";

const queryClient = useQueryClient();

// 当此修改成功时,将所有带有`todos`和`reminders`查询键值的查询都无效
const mutation = useMutation(addTodo, {
  onSuccess: () => {
    queryClient.invalidateQueries("todos");
    queryClient.invalidateQueries("reminders");
  },
});

11. 响应更新的数据 updates-from-mutation-responses

当我们更新一个数据对象时,新的数据通常会在更新成功后返回,此时我们可以通过queryClient.setQueryData方法,在前端直接更新对象,从而避免重新再请求一次

const queryClient = useQueryClient();
// 更新成功回调中,setQueryData,在todo query中找到id为5的
const mutation = useMutation(editTodo, {
  onSuccess: (data) => {
    queryClient.setQueryData(["todo", { id: 5 }], data);
  },
});

mutation.mutate({
  id: 5,
  name: "Do the laundry",
});

// 下面的查询将使用成功的更新响应来进行更新
const { status, data, error } = useQuery(["todo", { id: 5 }], fetchTodoByID);

总结

常用功能就是这些,具体的细节还是需要在开发的过程中查文档

 类似资料: