• 使用React-Query解决接口请求


    使用React-Query解决接口请求

    前言

    如果你平常会写前后端交互react页面,那你一定写过这样的代码.

    function App() {
    
      const [data, updateData] = useState(null);
      const [isError, setError] = useState(false);
      const [isLoading, setLoading] = useState(false);
      
      useEffect(async () => {
        setError(false);
        setLoading(true);
        try {
          const data = await axios.get('/api/user');
          updateData(data);
        } catch(e) {
          setError(true);
        }
        setLoading(false);
      }, [])
       
      if(isLoading) return ...
      
      if(isError) return ...
      
      return <List users={data}/>
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    这是一个组件拉取服务端数据的简单例子,在组件中,我们简单拉取了一个接口的数据,并监听接口的状态,根据状态来更新不同的UI。

    如果有性能上的要求,那这里可能还需要加上一段缓存的逻辑

    //...
        try {
    +      if(sessionUtil.get('users'){
    +          updateData(sessionUtil.get('users'));
    +          return;
    +      }
          const data = await axios.get('/api/user');
    +     sessionUtil.set('users',data)
          updateData(data);
        } catch(e) {
          setError(true);
        }
    //...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    至此,这个组件已经变得相当复杂了,如果组件拉取了好几个接口,那么这一套逻辑还得写好几遍。

    1 一些状态管理库的弊端

    许多状态管理库,比如redux,可以很流畅的管理页面的状态,也有处理副作用的能力,但往往不能很好的处理服务端的状态,因为处理服务端的状态,通常还包括:

    • 缓存
    • 将对同一数据的多个请求消除为一个请求
    • 在后台更新“过期”数据
    • 知道数据何时“过期”
    • 尽快反映数据更新
    • 性能优化,如分页和延迟加载数据
    • 管理内存和服务器状态的垃圾收集
    • 使用结构共享记忆查询结果

    直到React-Query的出现,上面的问题都变得迎刃而解。

    2 React-Query

    React Query 是一个开箱即用,零配置的服务端状态管理库,支持RestfulGraphQL两种类型的请求,它能帮助你很好的获取、同步、管理和缓存你的远程数据。它提供了几个简单的Hooks,借助它们可以很轻松的完成对后端数据的增删改查等操作,无需再写繁琐的数据拉取和状态判断等代码。

    React-Query的官方文档没有大纲,阅读起来相当不方便,个人感觉,直接阅读github源码项目中的docs要更方便一些。链接地址:https://github.com/TanStack/query/tree/main/docs/react

    2.1 简单使用

    2.1.1 QueryClientProvider

    首先,需要在组件外层定义一个queryClient作为组件操作和使用数据的一个共同容器,通过QueryClientProvider组件注入到项目中。

    import {
      QueryClient,
      QueryClientProvider,
      useQuery,
    } from '@tanstack/react-query'
    
    const queryClient = new QueryClient()
    
    export default function App() {
      return (
        <QueryClientProvider client={queryClient}>
          <Example />
        </QueryClientProvider>
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在创建QueryClient的时候,我们可以传入一些参数,用于管理项目中的请求、缓存、日志的相关配置,这些配置会对整个项目生效,其中包含了四个模块的配置参数。

    new QueryClient({
      queryCache?: QueryCache;
      mutationCache?: MutationCache;
      logger?: Logger;
      defaultOptions?: DefaultOptions;
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • queryCache: 请求缓存相关配置
    • mutationCache: 数据修改缓存相关配置
    • logger: 日志相关配置
    • defaultOptions:请求基础配置

    其中defaultOptions用于配置项目中useQuery请求的管理,常用的配置如下:

    • staleTime: 重新获取数据的时间间隔 默认0
    • cacheTime: 数据缓存时间 默认 1000 60 5 5分钟
    • retry: 失败重试次数 默认 3次
    • refetchOnWindowFocus: 窗口重新获得焦点时重新获取数据 默认 false
    • refetchOnReconnect: 网络重新链接
    • refetchOnMount: 实例重新挂载时重新拉取请求
    • enabled: 如果为“false”,“useQuery”不会触发,需要使用其返回的“refetch”来触发操作
    • queryFn:全局定义请求方法,其他地方使用时只需要直接传入请求参数
    2.1.2 useQuery

    useQueryReact-Query提供的用于请求接口并管理请求状态等信息的Hook。

    例如:

    function Example() {
      const { isLoading, error, data } = useQuery({
        queryKey: ['repoData'],
        queryFn: () =>
          fetch('https://api.github.com/repos/TanStack/query').then(
            (res) => res.json(),
          ),
      })
    
      if (isLoading) return 'Loading...'
    
      if (error) return 'An error has occurred: ' + error.message
    
      return (
        <div>
          <h1>{data.name}</h1>
          <p>{data.description}</p>
          <strong>👀 {data.subscribers_count}</strong>{' '}
          <strong>{data.stargazers_count}</strong>{' '}
          <strong>🍴 {data.forks_count}</strong>
        </div>
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    userQuery接收一个配置对象,其中

    • queryKey:必传,用作请求数据缓存的唯一key值,也可以在数组中,写入多项如:['repoData', '1'],这样React-Query在使用的时候会自动把它拼接为/repoData/1,这个在缓存用户访问过的页面时,非常有用。
    • queryFn:用于请求的方法,如果在QueryClient中配置了,这里可以不必再写,需要返回请求完成后所处理的数据。

    除了这两项基本的参数,useQuery还可以传入上面defaultOptions的所有参数,来表示对这个请求单独的配置。

    然后useQuery会返回一个对象,里面包含着请求相关的所有信息,这些信息会随着请求的进度而改变,就无须我们再使用一组state变量来进行管理了,常用的包括:

    • isLoading:请求是否正在进行
    • error:请求是否出错,这里只会对500404等做出反应,如果有其他情况错误情况,需要在请求方法里面throw
    • isSuccess: 请求是否成功
    • status: 请求状态,包含上面几种情况
    • data:返回数据

    除此之外,使用useQuery拉取回来的数据,会被默认缓存起来,然后可以通过配置过期时间,重新拉取等策略来进行管理。

    2.1.3 useQueryClient

    通过useQueryClient,我们可以获取到之前注入的容器实例,里面保存着所有我们缓存的信息,以及配置信息,而它本质上其实也是对React.useContext的封装。

    以上面Example组件为例,如果我们想在另一个组件访问这些数据。

    function Example() {
      const queryClient = useQueryClient()
    
      const data = queryClient.getQueryData(['repoData'])
    
      return (
        //...
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    除了访问缓存数据,queryClient还有很多API:

    大家可以根据需要去官网查阅。

    2.1.4 useMutation

    除了获取数据,很多时候还需要处理数据的修改,比如说最简单的todo list例子,除了拉取数据列表,还需要增删改数据,而这个时候除了需要发送接口,还需要修改本地的数据,React-Query提供了useMutation来帮我们完成这些事情。

    以上面Example组件为例,我们已经拉取到了data,现在我们想新增一条数据,那我们可以

    const {isLoading,isError,isSuccess,mutate} = useMutation({
        mutationFn: async (newData) => insertNewData(newData),
        onSuccess: () => {
          queryClient.invalidateQueries({ queryKey: ['repoData'] });
        },
        onError: (error) => {
          console.log(error)
        }
      });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这里我们传入了:

    • mutationFn:代表元数据的方法
    • onSuccess:接口调用成功后的回调
    • onError: 失败的回调

    返回的数据和useQuery基本是相同的,这里的mutate则是触发更改的方法,如果我们想执行useMutation中传入的方法,我们只需要调用mutate即可,传给mutate的参数都会被带到useMutation的构造方法中。

      const updateData = async (newData) => {
        mutate(newData);
      };
    
    • 1
    • 2
    • 3

    以上就是React-Query最核心的对服务端数据进行增删改查的功能,除此之外,React-Query还有很多其他的能力。

    2.2 数据预获取

    有时候我们不需要整个页面loading来等待数据加载,我们更希望在用户操作之前就拉取完数据,比如用户hover详情链接,而不是点击详情的时候。

    那我们可以使用queryClientprefetchQuery方法,提前拉取到用户可能会访问的数据,并加入到缓存中,由于不需要监听服务端状态等,所以这个方法会比useQuery高效许多。

    onMouseEnter={async () => {
      await queryClient.prefetchQuery({
        queryKey: ['character', char.id],
        queryFn: () => getCharacter(char.id),
        staleTime: 10 * 1000, // only prefetch if older than 10 seconds
      })
    }}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.3 分页缓存

    通过动态设置queryKey,并将keepPreviousData设置为true,我们可以很轻松的缓存之前页码的数据

      const { status, data, error, isFetching, isPreviousData } = useQuery({
        queryKey: ['projects', page],
        queryFn: () => fetchProjects(page),
        keepPreviousData: true,
        staleTime: 5000,
      })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.4 滚动列表渲染

    使用useInfiniteQuery定义拉取数据的方法,以及上下页的逻辑,然后会返回更新页面数据的状态,以及触发更新的方法。

      const {
        status,
        data,
        error,
        isFetching,
        isFetchingNextPage,
        isFetchingPreviousPage,
        fetchNextPage,
        fetchPreviousPage,
        hasNextPage,
        hasPreviousPage,
      } = useInfiniteQuery(
        ['projects'],
        async ({ pageParam = 0 }) => {
          const res = await axios.get('/api/projects?cursor=' + pageParam)
          return res.data
        },
        {
          getPreviousPageParam: (firstPage) => firstPage.previousId ?? undefined,
          getNextPageParam: (lastPage) => lastPage.nextId ?? undefined,
        },
      )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    2.5 devTools配套开发工具

    导入开发工具

    import { ReactQueryDevtools } from 'react-query/devtools'
    
    <ReactQueryDevtools initialIsOpen={false} />
    
    • 1
    • 2
    • 3

    默认情况下,当process.env.NODE ENV === 'production' 时开启 Devtools ,不必担心构建时需要排除他们

    浮动模式下开启,会将devtools作为固定的浮动元素安装在开发的应用程序中,并在屏幕一角提供一个切换按钮以显示和隐藏devtools

    在devtools中我们可以直观的看到已经缓存下来的数据和整个项目的配置,以及各个接口的状态等。

    1.png

    3 总结

    感谢你能看到这里,本文简单介绍了React-Query对服务端数据进行增删改查的功能实现,以及React-Query的一些其他能力,希望对你有用,React-Query的使用场景没有其他状态管理库那么广泛,但还是能解决很多服务器拉取数据的痛点。

  • 相关阅读:
    Python文件操作(05):Excel操作
    Python软件编程等级考试一级——20220915
    第十四届蓝桥杯模拟赛(第二期)
    《剑指Offer》二叉树全题——一套妙解,保证让你轻松掌握二叉树~
    以太坊之使用truflle和infura部署以太坊合约|猿创征文
    Mysql整理-索引
    docker运维之自定义网络配置
    cadence SPB17.4 - allegro - use potrace convert bmp to dxf, then import
    51单片机演奏兰亭序
    先来聊聊MySQL的binlog文件解析
  • 原文地址:https://blog.csdn.net/weixin_55756734/article/details/133015777