使用React-Query解决接口请求的麻烦事

前言

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

js 复制代码
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}/>
}

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

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

js 复制代码
//...
    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);
    }
//...

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

一些状态管理库的弊端

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

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

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

React-Query

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

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

简单使用

QueryClientProvider

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

js 复制代码
import {
  QueryClient,
  QueryClientProvider,
  useQuery,
} from '@tanstack/react-query'

const queryClient = new QueryClient()

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Example />
    </QueryClientProvider>
  )
}

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

js 复制代码
new QueryClient({
  queryCache?: QueryCache;
  mutationCache?: MutationCache;
  logger?: Logger;
  defaultOptions?: DefaultOptions;
})
  • queryCache: 请求缓存相关配置
  • mutationCache: 数据修改缓存相关配置
  • logger: 日志相关配置
  • defaultOptions:请求基础配置

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

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

useQuery

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

例如:

js 复制代码
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>
  )
}

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

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

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

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

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

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

useQueryClient

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

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

js 复制代码
function Example() {
  const queryClient = useQueryClient()

  const data = queryClient.getQueryData(['repoData'])

  return (
    //...
  )
}

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

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

useMutation

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

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

js 复制代码
const {isLoading,isError,isSuccess,mutate} = useMutation({
    mutationFn: async (newData) => insertNewData(newData),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['repoData'] });
    },
    onError: (error) => {
      console.log(error)
    }
  });

这里我们传入了:

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

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

js 复制代码
  const updateData = async (newData) => {
    mutate(newData);
  };

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

数据预获取

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

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

js 复制代码
onMouseEnter={async () => {
  await queryClient.prefetchQuery({
    queryKey: ['character', char.id],
    queryFn: () => getCharacter(char.id),
    staleTime: 10 * 1000, // only prefetch if older than 10 seconds
  })
}}

分页缓存

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

js 复制代码
  const { status, data, error, isFetching, isPreviousData } = useQuery({
    queryKey: ['projects', page],
    queryFn: () => fetchProjects(page),
    keepPreviousData: true,
    staleTime: 5000,
  })

滚动列表渲染

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

js 复制代码
  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,
    },
  )

devTools配套开发工具

导入开发工具

js 复制代码
import { ReactQueryDevtools } from 'react-query/devtools'

<ReactQueryDevtools initialIsOpen={false} />

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

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

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

最后

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

笔者之后也会继续更新React-Query的其他使用场景,如果可以的话,不妨点个赞再走呢,这对我很重要。

参考

推荐官网地址:github.com/TanStack/qu...

官方用例地址:github.com/TanStack/qu...

相关推荐
Dread_lxy26 分钟前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与2 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
前端郭德纲2 小时前
浏览器是加载ES6模块的?
javascript·算法
JerryXZR2 小时前
JavaScript核心编程 - 原型链 作用域 与 执行上下文
开发语言·javascript·原型模式
帅帅哥的兜兜2 小时前
CSS:导航栏三角箭头
javascript·css3
渗透测试老鸟-九青2 小时前
通过投毒Bingbot索引挖掘必应中的存储型XSS
服务器·前端·javascript·安全·web安全·缓存·xss
龙猫蓝图2 小时前
vue el-date-picker 日期选择器禁用失效问题
前端·javascript·vue.js
夜色呦2 小时前
掌握ECMAScript模块化:构建高效JavaScript应用
前端·javascript·ecmascript
peachSoda72 小时前
随手记:简单实现纯前端文件导出(XLSX)
前端·javascript·vue.js