人人都会AI编程

RTK Query:数据请求与缓存能力

更新时间:2026-07-10

RTK Query 是 Redux Toolkit(RTK)内置的数据请求和缓存解决方案,专注于 API 交互的完整生命周期管理。它并非独立的请求库,而是一个与 Redux Store 深度集成的缓存层,会自动处理数据获取、缓存、失效、并发请求去重和 UI 状态同步,让开发者无需手写大量样板代码,即可实现复杂的数据管理逻辑。

核心能力概览

  • 声明式端点定义:通过 createApi 统一声明所有 API 端点,涵盖查询(query)与变更(mutation)。
  • 智能缓存:自动缓存请求结果,相同参数的查询共享缓存,减少不必要的网络请求。
  • 自动重取:支持基于标签(tag)的缓存失效,数据变更后可精确触发相关查询重新获取。
  • 乐观更新:支持在 mutation 完成前先更新 UI,失败时自动回滚,提升交互流畅度。
  • 组件级数据生命周期:数据随组件挂载自动获取,组件卸载后定时清理缓存,避免内存泄漏。
  • 高级功能:内置分页、无限滚动、数据预取、轮询、background refetch 等开箱即用。

与现有 Redux 生态的关系

RTK Query 不是要取代 Axios 或 TanStack Query,而是 Redux 生态内的最佳实践。如果你的项目已使用 Redux Toolkit 管理全局状态,那么 RTK Query 是最自然的顺势扩展——它共享同一个 Redux Store,无需额外引入其他状态管理库。对于新项目,RTK Query 也可以独立使用(数据会被存储在它自动创建的 Redux slice 中),只需提供一次 store 配置。

快速上手

1. 创建 API 切片

使用 createApi 定义所有端点,并配置 baseQuery 确定数据请求方式(默认使用 fetchBaseQuery,内部基于 fetch,也可替换为 Axios 等)。

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const api = createApi({
  // 切片在 store 中的标识
  reducerPath: 'api',
  // 基础请求配置(baseUrl、headers 等)
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  // 标签定义,用于缓存失效
  tagTypes: ['Post'],
  // 端点定义
  endpoints: (builder) => ({
    // 查询端点:获取帖子列表
    getPosts: builder.query({
      query: (page = 1) => `posts?page=${page}`,
      // 为这个查询的结果打上 tag
      providesTags: ['Post'],
    }),
    // 查询端点:获取单个帖子
    getPost: builder.query({
      query: (id) => `posts/${id}`,
      providesTags: (result, error, id) => [{ type: 'Post', id }],
    }),
    // 变更端点:创建新帖子
    addPost: builder.mutation({
      query: (newPost) => ({
        url: 'posts',
        method: 'POST',
        body: newPost,
      }),
      // 使带有 'Post' 标签的缓存数据失效
      invalidatesTags: ['Post'],
    }),
  }),
});

2. 将 API 接入 Store

import { configureStore } from '@reduxjs/toolkit';
import { api } from './api';

export const store = configureStore({
  reducer: {
    // 将 api 的 reducer 注册到 store
    [api.reducerPath]: api.reducer,
  },
  // 添加 api 中间件,启用缓存、轮询、自动重取等功能
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(api.middleware),
});

3. 在组件中使用自动生成的 Hooks

createApi 会根据端点名称自动生成对应的 React Hooks(如 useXxxQueryuseXxxMutation),直接导入即可使用,无需手动 dispatch 或编写 thunk。

import { useGetPostsQuery, useAddPostMutation } from './api';

function PostList() {
  // 自动发起请求、管理 loading/error 状态
  const { data: posts, isLoading, error } = useGetPostsQuery(1);
  const [addPost, { isLoading: isAdding }] = useAddPostMutation();

  if (isLoading) return <div>加载中...</div>;
  if (error) return <div>请求失败</div>;

  return (
    <div>
      <button
        onClick={() => addPost({ title: '新帖子', content: '内容' })}
        disabled={isAdding}
      >
        {isAdding ? '发布中...' : '发布新帖子'}
      </button>
      <ul>
        {posts?.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

缓存机制与标签失效

RTK Query 默认采用 Stale-While-Revalidate 策略:首次请求成功后缓存数据,后续使用时先返回缓存(如果有),同时在后台发起新请求更新缓存。你可以通过 keepUnusedDataFor 配置缓存保留时间(组件卸载后继续保留一段时间,默认 60 秒),避免数据过早回收。

标签(Tags) 是控制缓存失效的关键:

  • 查询端点通过 providesTags 声明自己产出哪些 tag。
  • 变更端点通过 invalidatesTags 声明自己会使得哪些 tag 对应的数据失效。

当 mutation 执行后,所有带有被失效 tag 的查询将自动重新获取,保证 UI 数据与服务器同步。

例如上面的例子中,addPost 使 'Post' 标签失效,因此 getPosts 会自动重新拉取列表;而 getPost 也携带了 Post 标签,所以详情页也会同步更新。

乐观更新和高级用法

乐观更新:对于交互响应要求极高的场景,可以在 mutation 的 onQueryStarted 中手动更新缓存,实现即时 UI 反馈,网络返回后验证或回滚。

endpoints: (builder) => ({
  updatePost: builder.mutation({
    query: ({ id, ...patch }) => ({
      url: `posts/${id}`,
      method: 'PATCH',
      body: patch,
    }),
    // 乐观更新示例
    async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
      // 立即更新缓存中的对应帖子
      const patchResult = dispatch(
        api.util.updateQueryData('getPost', id, (draft) => {
          Object.assign(draft, patch);
        })
      );
      try {
        await queryFulfilled; // 等待真实请求完成
      } catch {
        patchResult.undo(); // 失败时回滚
      }
    },
  }),
})

分页与无限滚动:RTK Query 支持 mergeforceRefetch 等参数,配合数据合并逻辑可实现无限滚动。更便捷的方式是直接使用 useInfiniteQuery(RTK Query 2.0+ 新特性)或结合 React 虚拟列表。

集成 Axios 等自定义请求

通过 fetchBaseQuery 已经可以覆盖大部分场景,但如果需要拦截器、进度监听等高级能力,可以创建自定义 baseQuery

import axios from 'axios';

const axiosBaseQuery = ({ baseUrl } = { baseUrl: '' }) =>
  async ({ url, method, data, params }) => {
    try {
      const result = await axios({ url: baseUrl + url, method, data, params });
      return { data: result.data };
    } catch (axiosError) {
      return {
        error: {
          status: axiosError.response?.status,
          data: axiosError.response?.data || axiosError.message,
        },
      };
    }
  };

何时选择 RTK Query

  • ✅ 项目已使用 Redux Toolkit,需要统一数据状态管理。
  • ✅ 团队希望减少样板代码,用声明式方式管理 API 请求。
  • ✅ 需要开箱即用的缓存、自动重取、乐观更新等特性。

如果项目没有 Redux,且不需要全局状态管理(如仅用 React Query 就能满足),那么 RTK Query 可能稍显沉重——但它的学习曲线平缓,且能为后续扩展提供统一的状态管理基础。