查询架构模式:API层、Key策略与失效设计

API客户端层与 Query 封装策略

你要搭的,是一个分层清晰的“查询架构”:
API 客户端只管 HTTPQuery 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 层)
  • 决定缓存策略(staleTimegcTime 等)
  • 定义返回给 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 提供的字段,如 dataisLoadingerror,而不关心 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