API客户端层与 Query 封装策略
你要搭的,是一个分层清晰的“查询架构”:
API 客户端只管 HTTP,Query Hook 只管缓存和数据形态,UI 只消费 Hook。
API 层:只做“怎么请求”
API 层的职责:
- 拼 URL、路径、查询参数
- 设置请求方法、headers、认证信息
- 解析原始响应(如
res.data) - 抛出合适的错误(例如统一在 4xx/5xx 时抛错)
典型实现(用 Axios 举例):
ts// api/posts.ts
import { axios } from "./axiosInstance"
export interface Post {
id: string
title: string
content: string
}
export interface PostsListFilter {
page?: number
pageSize?: number
search?: string
}
export const postsApi = {
async list(filter: PostsListFilter) {
const res = await axios.get("/posts", { params: filter })
return res.data as { items: Post[]; total: number }
},
async detail(id: string) {
const res = await axios.get(`/posts/${id}`)
return res.data as Post
},
async create(input: { title: string; content: string }) {
const res = await axios.post("/posts", input)
return res.data as Post
},
async update(id: string, input: { title?: string; content?: string }) {
const res = await axios.patch(`/posts/${id}`, input)
return res.data as Post
}
}
这里的 postsApi 不知道 React、不知道 缓存,只知道“如何对后端说话”。
Query Hook 层:只做“数据怎么缓存和暴露给 UI”
Query Hook 的职责:
- 定义 Query Key(标识缓存单元)
- 定义 查询函数(通常调用 API 层)
- 决定缓存策略(
staleTime、gcTime等) - 定义返回给 UI 的数据形态(可能做轻度整理)
例子(以 React Query 为例):
ts// queries/posts.ts
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import { postsApi, PostsListFilter } from "../api/posts"
import { postsQueryKeys } from "./postsQueryKeys"
// 列表查询
export function usePostsList(filter: PostsListFilter) {
return useQuery({
queryKey: postsQueryKeys.list(filter),
queryFn: () => postsApi.list(filter),
staleTime: 1000 * 30 // 30s 内视为新鲜
})
}
// 详情查询
export function usePostDetail(id: string) {
return useQuery({
queryKey: postsQueryKeys.detail(id),
queryFn: () => postsApi.detail(id),
enabled: !!id
})
}
UI 组件只关心这些 Hook 提供的字段,如 data、isLoading、error,而不关心 HTTP 细节。
UI 层:只消费 Hook
tsx// components/PostsListPage.tsx
import { usePostsList } from "../queries/posts"
export function PostsListPage() {
const { data, isLoading, error } = usePostsList({ page: 1, pageSize: 10 })
if (isLoading) return <div>Loading...</div>
if (error) return <div>Something went wrong</div>
return (
<ul>
{data?.items.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
组件完全不知道 axios 或 /posts,它只说:
“给我第一页 posts 列表”,其余交给 Hook 和 API 层。
一旦 API 层里开始直接调用 useQuery,或在组件里直接写 axios.get,后续所有缓存策略、重用逻辑都会散落在各处,很难统一改。
1 / 6