react-query 维护服务器的处理状态
😋 react-query 维护服务器的处理状态
2022/6/5 10:03:00
➡️

来源

按照来源,前端有两类「状态」需要管理:

  1. 用户交互的中间状态 简单的用 useStatue useContext 进行管理,复杂的用 react redux
  2. 服务端状态 可以自己封装 hooks 进行处理,但是涉及多余请求合,并缓存,重连,重试等状态就比较复杂了

Tanner Linsley 开发的好用的处理服务器数据状态的 hooks react-query 是专门做服务端状态处理的不但可以应对 api 数据也可以应对 graphql

中文文档 英文文档

数据的CRUD由2个hook处理:

  • useQuery 处理数据的查
  • useMutation 处理数据的增/删/改

缓存

SWR(stale-while-revalidate)

缓存表示的是当将当前请求成功的数据缓存起来,在组件重新加载的时候,如果有缓存数据,会优先返回缓存数据,然后在背后发送新请求 缓存失效的时间是当一个 query key 的数据没有挂载点或没有观察者(界面上没有引用)的时候,开始进行计时, 时间到这个 query key 就会在缓存内删除,如果在这段时间内 query key 有了新的挂载点,缓存就不会失效

保鲜的失效的开始计算时间是跟随缓存失效倒计时的开始时间的,同时保鲜的时间段是小于等于缓存的时间段的

有俩个参数可以设置

  • cacheTime 缓存时间 如果数据在缓存时间内,判断 staleTime 是否大于0 ,如果大于 0 进行 staleTime 判断,否则返回缓存数据,在进行数据查询, 之后用查询回来的数据更新界面及缓存 ,如果数据不在缓存时间内,立即进行查询

    此数值默认为5分钟

  • staleTime 数据保鲜时间 如果数据还在缓存时间里,则判断数据是否在保鲜时间内,如果在的话,就不会发起查询,直接用缓存数据,不在的话先返回缓存数据,在进行数据查询, 之后用查询回来的数据更新界面及缓存

    此数值默认为0

// 设计巧妙的 返回 promise 函数模拟查询服务器数据
function getName(): Promise<string> {
  return new Promise((resolve, reject) => {
    //返回一个随机的 bool 变量
    var rand = Boolean(Math.round(Math.random()));
    // 返回一个随机的时间,模拟网络延时
    const time = Math.random() > 0.5 ? 3000 : 500;

    //随机的返回一个 reject 或 resolve
    if (rand === false) {
      setTimeout(() => {
        console.log("发生错误");
        reject(new Error("发生了错误"));
      }, time);
    } else {
      setTimeout(() => {
        console.log("获取数据成功");
        resolve(Mock.mock("@name"));
      }, time);
    }
  });

  //用来展示数据的组件
  function FuncComp({ name}: { name: string}) {
    console.log(name + ":rending");

    // useQuery 查询
    const { data, error, isLoading, isError, isFetching, refetch } = useQuery(["key1"], 
    () => getName(), {
      refetchOnWindowFocus: false,
      // 数据保鲜期,在保鲜期内不会去查询数据
      staleTime: 3 * 1000,
      //数据的缓存时间
      cacheTime: 15 * 1000,
    });

    return (
      <div>
        {isLoading && <> {name}:Loading...</>} <br/>
        {isFetching && <>{name}:Fetching...</>} <br/>
        {isError && (<>{name}:{(error as Error).message} </>)} <br/>
        {data && (<>{name}:{data}</>)} <br/>
        <br/>

        <button 
          // 强制刷新数据
          onClick={() => refetch()}>
          {name}+:refetch
        </button>
      </div>
    );
  }
}

// 通过 toggle 来显示或卸载组件,来测试 SWR 的概念
// FuncComp 卸载的时间超过 cacheTime ,在此显示组件缓存数据会消失
// FuncComp 卸载的时间不超过 cacheTime,在 staleTime 内不会发起新的查询,否则会发起新的查询,
//无论如何都会先返回缓存的数据
function App() {
  const [status, { toggle }] = useToggle()
  return (
    <>
      {status && <FuncComp name="组件A" para={true} ></FuncComp>}
      <br/>
      <button onClick={toggle}>显示组件 FuncComp</button>
    </>
  );
}

只要有挂载点或观察者缓存永远不会失效

 // 对上个例子的组件用如下方式使用
 // 无论任何时候 query 都有一个挂载点或观察者,那 缓存就不可能会失效,那么数据就一直是处于保鲜状态的,  
 // 无论如何切换组件,显示的数据就不会更新,除非手动的更新数据或手动的使缓存数据失效 
 
 // 在一个组件内强制 refetch 数据,切换回另一个组件的时候,界面也会更新,这个就是状态管理的全局性
 function App() {
  const [status, { toggle }] = useToggle()
  return (
    <>
      {status ? <FuncComp name="组件A" para={true} ></FuncComp>:<FuncComp name="组件B" 
      para={true} ></FuncComp>}
      <br/>
      <button onClick={toggle}>显示组件 FuncComp</button>
    </>
  );
}

query key 内包含状态数据

function FuncComp({ name}: { name: string}) {
  console.log(name + ":rending");

  const [status, setStatus] = useState(false);
  const { data, error, isLoading, isError, isFetching, refetch } = useQuery(["key1", status],
    () => getName(), {
    refetchOnWindowFocus: false,
    // 数据保鲜期,在保鲜期内不会去查询数据
    staleTime: 15 * 1000,
    //数据的缓存时间
    cacheTime: 15 * 1000,
  });

  return (
    <div>
      {isLoading && <> {name}:Loading...</>} <br/>
      {isFetching && <>{name}:Fetching...</>} <br/>
      {isError && (<>{name}:{(error as Error).message} </>)} <br/>
      {data && (<>{name}:{data}</>)} <br/>
      <br/>
      <button
        // 可以通过改变 queryKey 重新获取数据
        onClick={() => { setStatus((x) => !x); }}
      >
        {name}:click_{String(status)}
      </button>

      <button onClick={() => refetch()}>
        {name}+:refetch
      </button>
    </div>
  );
}
👍🎉🎊