这篇文章系统介绍了 TanStack Query 在前端请求状态管理中的应用,核心围绕三个关键模块:useQuery(响应式数据获取,支持自动刷新和条件请求)、useMutation(处理副作用操作,提供完整生命周期回调)和 queryClient(请求中继器,支持手动缓存控制)。文章重点阐述了项目实践规范,包括分层架构设计(api/query/type 文件分离)、类型安全规范(DTO/VO 实体定义)以及响应式参数处理技巧(maybeRefOrGetter + toValue 组合),最后还提及了通过 IDE 插件实现代码规范自动化生成的高效开发模式。

TanStack Query 请求状态管理

什么是 Tanstack Query?为什么要使用它

tanstackQuery 我个人认为是一个请求调用的效率提升工具;帮助开发者基于响应式获取请求数据,基于函数式规范调用接口;

tanstackquery 主要分为两部分实现,一部分是 useQuery,一部分是 useMutation;
两部分实现都是对请求的抽象调用和获取,我们可以简单的理解为,useQuery 是用于自动化(响应式)获取请求,而 useMutation 用于手动调用请求;
及后文会提到的 queryClient,掌握这三者的用法可以帮助你快速开发项目;

useQuery

  • 有了 useQuery 之后,我们不再需要手动请求接口,改为响应式获取值
1
2
3
4
5
6
7
8
9
const { data } = useQuery({
...blogQuery.getBlogDetail(() => ({
id: id.value,
})),
enabled: () => !!id.value,
});
watchEffect(() => {
console.log(data.value); // data是响应式的
});
  • 对于用户操作导致的接口数据更新,我们不再需要借助 store/emit 等通信方式,直接调用 queryClient.invalidateQueries()刷新请求
1
2
3
4
5
6
7
8
9
10
11
// 父组件
const { data } = useQuery({
...blogQuery.getBlogDetail(() => ({
id: id.value,
})),
enabled: () => !!id.value,
});

// 子组件 刷新
const queryClient = useQueryClient();
queryClient.invalidateQueries();
  • 控制 useQuery 是否能执行
    useQuery 自身具备控制请求是否能执行的函数:enabled

enabled 是一个响应式参数,可以传入()=>xxx.value 控制

示例:

1
2
3
4
5
6
7
const id = ref();
useQuery({
...blogQuery.getBlogDetail(() => ({
id: id.value,
})),
enabled: () => !!id.value,
});

常见的使用 useQuery 的场景有:
列表类数据初始化获取

useMutation

useMutation 是一个回调函数式的 API,他有 onSuccess,onError,onSettled 回调,很方便的让使用者做不同阶段的操作;下面是一个示例

1
2
3
4
5
6
7
8
9
10
11
12
13
const { mutate: createBlog } = useMutation({
mutationFn: blogAPI.createBlog,
onSuccess(data) {
// 成功回调
},
onError(err) {
// 错误提示 上报等
},
onSettled() {
// 创建后刷新列表
queryClient.invalidateQuries();
},
});

常见的使用 useMutation 的场景有:
post,put,delete 等请求

queryClient

queryClient 是 tanstackQuery 的请求中继,不依赖 setup,更像是对 api 的直接调用,但是他有如下的好处

  • queryClient.fetchQuery() 手动调用请求,无视缓存

我个人更喜欢在 useXXXDialog.tsx 中使用他,因为 naive-ui 的 dialog 是函数式创建的,尽量少写副作用较好

1
2
3
4
5
const data = await queryClient.fetchQuery(
blogQuery.getBlogDetail({
id: id.value,
})
);
  • queryClient.ensureQuery() 手动调用请求,优先从缓存中寻找,再则调用请求

这个使用的话更倾向于缓存优先,特别是有些时候在项目中明确这个请求是基于缓存的时候,就可以优先使用它

开发规范

文件夹规范

1
2
3
apis / blog / api.ts; // 存放封装了请求库的api合集
apis / blog / query.ts; // 存放需要tanstack query响应式请求的请求合集
apis / blog / type.ts; // 存放api类型声明

API 规范

在 query 中,我们需要借助 maybeRefOrGetter 和 toValue 实现响应式请求参数的导入和解析

1
2
3
4
5
6
7
export const blogQuery = {
getBlogDetail: (DTO: DTOHelper<typeof blogAPI.getBlogDetail>) =>
queryOptions({
queryKey: ["blogQuery", "getBlogDetail", DTO],
queryFn: () => blogAPI.getBlogDetail(toValue(DTO)),
}),
};
1
2
3
export type DTOHelper<T extends (...args: any) => any> = MaybeRefOrGetter<
Parameters<T>[0]
>;

命名规范

主要了解三个概念:实体(Entity),DTO,VO
参考:https://www.cnblogs.com/JerryMouseLi/p/11069237.html#vo%E8%A7%86%E5%9B%BE%E6%A8%A1%E5%9E%8B-%E4%B8%8E-dto%E6%95%B0%E6%8D%AE%E4%BC%A0%E8%BE%93%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%8C%BA%E5%88%AB

命名规范则为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface BlogEntity {
id: number;
url: string;
}

export interface GetBlogListVO {
lists: BlogEntity[];
}

export interface GetBlogDetailDTO {
id: number;
}

export interface GetBlogDetailVO extends BlogEntity {}

插件/AI 快速生成规范

借助 Vscode Module Templates 插件 / AI