使用React Query做为axios请求库的上层封装

2372次阅读  |  发布于2年以前

前言

在项目中,通常都需要跟服务端进行异步的数据交互,基本都是用到axios这个库来做请求,嗯,毕竟拥有80k star,明星项目

接下来,我们来回顾下axios在项目中的使用

以查询用户信息为例,我们会这样封装

async function requestUsers(){
  const {data} =await axios.get('/api/users');
  return data;
}

我们再用hooks再封装下这个请求,包括loading等中间态的封装,处理的优雅一点

import React, {useState,useEffect} from 'react';
import axios from 'axios';
function useUsersQuery(){
  const [data,setData] = useState([]);
  const [isLoading,setLoading] = useState(false);
  const [isError,setError] = useState(false)
  useEffect(()=>{
    (async()=>{
       setLoading(true);
       try{
          const {data} = await axios.get('/api/users');
          setData(data);
       } catch((()=>{
          setError(true);
       })
       setLoading(false);
    })()
  })
  return { 
     data,
     isLoading,
     isError
  };
}
function UserList(){
  const {data, isLoading,isError} = useUsersQuery();
  if (isLoading) {
        return <div>loading</div>;
   }
  if (isError) {
        return <div>error</div>;
   }
  return (
    <div>
       {
         data.map((item)=>{
            return <div>{item.name}</div>
         })
       }
    </div>
  )
}

可以看到,我们的项目中基本上是这样封装请求,我们不仅要请求数据,还要处理相应的loading,error这些中间态,这类通用的中间状态处理逻辑可能在不同组件中重复写很多次。

另外,现在的前端项目特别是单页面应用,会使用Flux、Redux、Mobox等状态管理库,会把组件间共享的数据都存放在状态管理库中,这些可以分为两类,一类是用户交互的中间状态,比如isLoading,isClose,modalVisible等等,另外一类就是服务端状态(数据)

我们一般处理的方式都是无差别的存放在全局状态管理上,状态管理库为了兼容异步请求,就有了redux-saga,redux-action这些异步解决方案

其实对于redux等状态管理库,本身是没有异步这个概念,只有mutation这种操作,为了支持异步,硬是强加了异步action这种操作,实际这些异步中间件就是在最后的请求回调透传了dispatch,诸如这些情况,我们不仅将数据一锅炖放在全局状态管理上,写法上也使得项目越来越臃肿了(以至于出现后面rematch、dva方案进行简化),我们有没有想过,服务端的状态就不应该放在全局状态管理上,全局状态管理应该专门处理用户交互的中间状态

接下来,就是引出今天的主角 React Query

React Query

React Query 通常被描述为 React 缺少的数据获取(data-fetching)库,但是从更广泛的角度来看,它使 React 程序中的获取,缓存,同步和更新服务器状态变得轻而易举。

解决了什么问题

服务端状态有以下特点:

  1. 存储在远端,本地无法直接控制
  2. 需要异步 API 来查询和更新
  3. 可能在不知情的情况下,被另一个请求方更改了数据,导致数据不同步

现有的状态管理库(如 Mobx、Redux等)适用于管理客户端状态,但它们并不关心客户端是如何异步请求远端数据的,所以他们并不适合处理异步的、来自服务端的状态。

而 React Query 就是为了解决服务端状态带来的上述问题而出现的,除此之外它还带来了以下特性:

  1. 更方便地控制缓存
  2. 把对于相同数据的多个请求简化成一个
  3. 在后台更新过期数据
  4. 知道数据什么时候会「过期」
  5. 对于数据的变化尽可能快得做出响应
  6. 分页查询和懒加载等请求性能优化
  7. 管理服务器状态的内存和垃圾回收
  8. 通过结构共享(structural sharing)来缓存查询结果

请求中间态处理

 function Todos() {
   const { isLoading, isError, data, error } = useQuery('todos', fetchTodoList)

   if (isLoading) {
     return <span>Loading...</span>
   }

   if (isError) {
     return <span>Error: {error.message}</span>
   }

   // also status === 'success', but "else" logic works, too
   return (
     <ul>
       {data.map(todo => (
         <li key={todo.id}>{todo.title}</li>
       ))}
     </ul>
   )
 }

React query会自动把这些isLoading,isError请求中间态处理好,我们不必写重复逻辑,另外配合Suspense提对一点对于loading场景的处理,Suspense也支持的不错,特别是局部Loading,简直Nice!

ReactQuery 的状态管理

Fetch, cache and update data in your React and React Native applications all without touching any "global state".

官网对于React Query的简述,注意global state,你会不解,为什么React Query明明是一个请求库,跟数据状态管理又有什么关系,甚至可以处做全局状态管理

那是因为ReactQuery 会在全局维护一个服务端状态树,根据 Query key 去查找状态树中是否有可用的数据,如果有则直接返回,否则则会发起请求,并将请求结果以 Query key 为主键存储到状态树中。

ReactQuery 就将我们所有的服务端状态维护在全局,并配合它的缓存策略来执行数据的存储和更新。借助于这样的特性,我们就可以将所有跟服务端进行交互的数据从类似于 Redux 这样的状态管理工具中剥离,而全部交给 ReactQuery 来管理。

举个例子:

import React from "react";
import { useQuery, queryCache } from "react-query";
import "./styles.css";

export default function App() {
  return (
    <div className="App">
      <h1>Shared state using react-query</h1>
      <Comp1 />
      <Comp2 />
    </div>
  );
}

function useSharedState(key, initialValue) {
  const { data: state } = useQuery(key, () => queryCache.getQueryData(key), {
    initialData: initialValue
  });

  const setState = value => queryCache.setQueryData(key, value);

  return [state, setState];
}

function Comp1() {
  const [count, setCount] = useSharedState("count", 1);

  console.log("comp1 rendered");

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>add</button>
    </div>
  );
}

function Comp2() {
  const [count, setCount] = useSharedState("count", 2);

  console.log("comp2 rendered");

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>add</button>
    </div>
  );
}

上述方式是可以实现React Query状态管理,但是有性能问题,其实本质还是利用Context透传,我们知道Context处理prop drilling问题,但是有性能问题,详情可查看这篇文章[ 精读《React — 5 Things That Might Surprise You》]

不过令人费解的是官方强调ReactQuery 的状态管理,但是在官网例子并没有给出类似的例子,上述例子还是在官方的github仓库翻到

作者说会在一个讲座分析,后面我再深入研究,先留个坑

参考文献

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8