react应用的数据请求和缓存方案选型
背景
现在开发的前端项目基本流程是从服务端获取数据。其中的全局数据会在项目初始化时使用 redux 保存在内存中,比如参数管理部分;局部数据会在每次使用时发起新的请求,比如节点管理部分。
这样的流程在实践过程中存在以下问题
- 数据获取的逻辑处理比较原始,风格不统一,实现过程所需时间较长且不宜维护,比如 loading 状态等每次需要手动维护,当修改全局数据后需要手动调用对应接口进行更新等。
- 页面每次加载都会发起新的请求,造成很多不必要的数据传输和加载等待时间。
因此我们需要对这一过程进行优化,以提高代码质量和节约时间,进而增加人效(格局打开 🤏)。
解决方案选择
总结一下,我们需要的是一个数据请求和缓存管理的工具。
目前比较流行的包括以下三个:
其中SWR
是简化版的react-query
,更轻量但是功能很少(主要提供了useSWR
一个 hook,其他需要自己实现),因此暂不考虑。
另外两个功能基本一致,具体对比可参考这里,其中react-query
使用更为广泛。
但是这里依然推荐RTK Query
,理由如下
RTK Query
是redux
团队提供的基于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
将数据请求分为 query
和 mutation
两种 ,语义上前者表示查询,后者表示对服务端数据有修改的操作。
为了适应每种场景,每种请求会包含多种类型的 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"],
}),
}),
});
乐观更新
比如修改列表中的一项数据时,我们可以不重新请求列表,而直接修改缓存或者先修改缓存等列表请求后再重新赋值,提高页面响应性能,具体可参考这里。
转载自:https://juejin.cn/post/7057779073438711815