react应用的数据请求和缓存方案选型

·  阅读 1021

背景

现在开发的前端项目基本流程是从服务端获取数据。其中的全局数据会在项目初始化时使用 redux 保存在内存中,比如参数管理部分;局部数据会在每次使用时发起新的请求,比如节点管理部分。

这样的流程在实践过程中存在以下问题

  • 数据获取的逻辑处理比较原始,风格不统一,实现过程所需时间较长且不宜维护,比如 loading 状态等每次需要手动维护,当修改全局数据后需要手动调用对应接口进行更新等。
  • 页面每次加载都会发起新的请求,造成很多不必要的数据传输和加载等待时间。

因此我们需要对这一过程进行优化,以提高代码质量和节约时间,进而增加人效(格局打开 🤏)。

解决方案选择

总结一下,我们需要的是一个数据请求和缓存管理的工具。

目前比较流行的包括以下三个:

其中SWR是简化版的react-query,更轻量但是功能很少(主要提供了useSWR一个 hook,其他需要自己实现),因此暂不考虑。

另外两个功能基本一致,具体对比可参考这里,其中react-query使用更为广泛。

但是这里依然推荐RTK Query,理由如下

  • RTK Queryredux团队提供的基于redux的数据请求和缓存工具,目前项目已经广泛使用redux,对现有代码影响较小,且能继续使用redux生态丰富的工具,比如redux devtools
  • 之前为了简化 redux 的使用安装过redux-toolkit,不需要安装另外的包。

缺点是去年 6 月刚刚发布,issues 等资料较少,但这个类型的库本身并不复杂,文档比较全,根据目前使用情况来看影响不大。

RTK Query 的具体介绍

RTK Query 提供的功能中我们日常使用较多的比如

  • 根据配置自动生成对应的请求 hook,并会返回 loading 等状态
  • 通过缓存和验证更新,避免对相同数据的重复请求
  • 通过乐观更新提高 UI 响应速度

基本使用

使用createApi创建每个 endpoint,每个 endpoint 对应一个网络请求,并返回对应的 hook

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import type { Pokemon } from "./types";

// Define a service using a base URL and expected endpoints
export const pokemonApi = createApi({
  reducerPath: "pokemonApi",
  //这里执行实际的http请求
  baseQuery: fetchBaseQuery({ baseUrl: "https://pokeapi.co/api/v2/" }),
  //这里定义所有的endpoint
  endpoints: (builder) => ({
    getPokemonByName: builder.query<Pokemon, string>({
      query: (name) => `pokemon/${name}`,
    }),
  }),
});

//这里是自动生成的对应hook
export const { useGetPokemonByNameQuery } = pokemonApi;
复制代码

hook 在组件中的使用

import { getPokemonByName } from "./api";

function MaybePost({ name }: { name: string }) {
  const { data } = getPokemonByName(name);

  return <div>...</div>;
}
复制代码

具体使用可参考redux 文档

进阶使用

自定义请求方法

baseQuery选项中的fetchBaseQuery封装了fetch,可以直接使用也可以利用项目中自定义过的axios

const baseQuery = (): BaseQueryFn<AxiosRequestConfig> => async (params) => {
  try {
    //封装过的axios实例
    let result = await commonAxiosInstance(params);
    return { data: result.data };
  } catch (axiosError) {
    let err = axiosError as AxiosError;
    return {
      error: { status: err.response?.status, data: err.response?.data },
    };
  }
};
复制代码

其中的错误处理除了使用 axios 中的拦截以外,还可以使用对应中间件

export const rtkQueryErrorLogger: Middleware = (api: MiddlewareAPI) => (
  next
) => (action) => {
  // RTK Query uses `createAsyncThunk` from redux-toolkit under the hood, so we're able to utilize these matchers!
  if (isRejectedWithValue(action)) {
    console.warn("We got a rejected action!");
    const msg = action?.payload?.data?.message;
    msg && message.error(msg);
  }

  return next(action);
};
复制代码

hook 种类

RTK Query将数据请求分为 querymutation 两种 ,语义上前者表示查询,后者表示对服务端数据有修改的操作。
为了适应每种场景,每种请求会包含多种类型的 hook

各类 hook 对比见这里

缓存的管理

缓存是该类工具的核心功能,根据endpoint + serialized arguments作为可复用标记。 除了直接使用缓存的方法外,还提供了无论缓存是否有效都重新请求的方法。

缓存的有效期默认 60 秒,即当使用当前缓存的组件为 0 达 60 秒后自动清除。

为了在缓存失效时自动请求,需要设置相关tags,即在mutaion中设置invalidatesTags表示当进行相关操作后对应缓存失效,在query中设置providesTags表示对应缓存失效已经自动请求以更新缓存。

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query";
import { Post, User } from "./types";

const api = createApi({
  baseQuery: fetchBaseQuery({
    baseUrl: "/",
  }),
  tagTypes: ["Post"],
  endpoints: (build) => ({
    addPost: build.mutation<Post, Omit<Post, "id">>({
      query: (body) => ({
        url: "post",
        method: "POST",
        body,
      }),
      invalidatesTags: ["Post"],
    }),
    editPost: build.mutation<Post, Partial<Post> & Pick<Post, "id">>({
      query: (body) => ({
        url: `post/${body.id}`,
        method: "POST",
        body,
      }),
      invalidatesTags: ["Post"],
    }),
  }),
});
复制代码

乐观更新

比如修改列表中的一项数据时,我们可以不重新请求列表,而直接修改缓存或者先修改缓存等列表请求后再重新赋值,提高页面响应性能,具体可参考这里

分类:
前端
收藏成功!
已添加到「」, 点击更改