创见博客
RTK Query
七崽爱吃小饼干2026/01/15阅读 0专栏 React

RTK Query 详解

RTK Query 是 Redux Toolkit 内置的数据请求与缓存管理工具,专为解决 Redux 项目中数据获取、缓存同步、状态维护等痛点设计。它基于 Redux 生态,无需手动编写 action、reducer 和 selector 来处理异步请求逻辑,大幅简化了数据交互流程。

一、核心定位与优势

1. 核心定位

RTK Query 不是独立的请求库,而是基于 Redux 的数据管理层,底层依赖 fetch 或 axios 等请求工具,专注于请求状态管理、数据缓存、自动重请求等上层能力。

2. 核心优势

  • 减少样板代码:无需手动定义请求相关的 action type、action creator、reducer。
  • 自动缓存与失效:请求结果自动缓存,支持配置缓存时间、失效策略,避免重复请求。
  • 请求状态管理:内置 loading/error/success 等状态,无需手动维护。
  • 数据预取与更新:支持组件挂载前预加载数据,以及主动触发数据重请求。
  • 与 Redux 无缝集成:数据存储在 Redux Store 中,可通过 useSelector 访问,也支持与其他 Redux 中间件配合使用。

二、核心概念

1. API Slice

createApi 是 RTK Query 的核心入口,用于创建API Slice,定义一组相关的请求接口配置。

  • 每个 API Slice 对应一个业务模块(如用户模块、商品模块)。
  • 配置项包括请求基础路径、请求拦截器、响应转换器、缓存策略等。

2. Endpoint

Endpoint 是 API Slice 中定义的单个请求接口,对应一个具体的 API 端点(如 GET /users、POST /products)。

  • 分为两种类型:
    • 查询(Query):用于获取数据的请求(如 GET),默认开启缓存。
    • 突变(Mutation):用于修改数据的请求(如 POST/PUT/DELETE),默认不缓存,触发后通常需要更新缓存。

3. Hooks 自动生成

RTK Query 会根据定义的 Endpoint 自动生成 React Hooks,供组件直接调用,无需手动封装。

  • Query 类型的 Endpoint 生成 useXXXQuery 钩子。
  • Mutation 类型的 Endpoint 生成 useXXXMutation 钩子。

4. 缓存与标签(Tags)

标签(Tags) 是 RTK Query 实现缓存更新的核心机制,用于标记缓存数据的所属类别。

  • 给 Query 请求的响应数据打上标签(如 ["User"])。
  • 当 Mutation 请求修改数据后,通过 invalidatesTags 使对应标签的缓存失效,触发 Query 自动重请求,保证数据一致性。

三、基本使用流程

以 React 项目为例,完整使用 RTK Query 分为 4 步:

步骤 1:安装依赖

确保已安装 @reduxjs/toolkit 和 react-redux:

bash
npm install @reduxjs/toolkit react-redux

步骤 2:创建 API Slice

使用 createApi 定义请求配置,以用户模块为例:

typescript
// src/services/userApi.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import type { User } from '../types';

// 定义 API Slice
export const userApi = createApi({
  // 必选:缓存 reducer 的唯一标识,会挂载到 Redux Store 的该路径下
  reducerPath: 'userApi',
  // 必选:基础请求函数,基于 fetch 封装,支持配置 baseUrl、请求头
  baseQuery: fetchBaseQuery({ baseUrl: 'https://api.example.com' }),
  // 可选:定义标签类型,用于缓存管理
  tagTypes: ['User'],
  // 必选:定义 Endpoint 集合
  endpoints: (builder) => ({
    // 1. Query Endpoint:获取用户列表
    getUsers: builder.query<User[], void>({
      query: () => '/users', // 请求路径
      providesTags: ['User'], // 给响应数据打标签
    }),
    // 2. Query Endpoint:根据 ID 获取单个用户
    getUserById: builder.query<User, number>({
      query: (id) => `/users/${id}`,
      providesTags: (result, error, id) => [{ type: 'User', id }], // 动态打标签(带 ID)
    }),
    // 3. Mutation Endpoint:添加用户
    addUser: builder.mutation<User, Partial<User>>({
      query: (newUser) => ({
        url: '/users',
        method: 'POST',
        body: newUser,
      }),
      invalidatesTags: ['User'], // 使 User 标签的缓存失效
    }),
  }),
});

// 自动生成 Hooks,命名规则:use + Endpoint 名 + Query/Mutation
export const { useGetUsersQuery, useGetUserByIdQuery, useAddUserMutation } = userApi;

步骤 3:配置 Redux Store

将 API Slice 的 reducer 和中间件添加到 Store:

typescript
// src/store.ts
import { configureStore } from '@reduxjs/toolkit';
import { userApi } from './services/userApi';

export const store = configureStore({
  reducer: {
    // 挂载 API Slice 的 reducer
    [userApi.reducerPath]: userApi.reducer,
  },
  // 添加 RTK Query 的中间件,用于处理缓存、重请求等逻辑
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(userApi.middleware),
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

步骤 4:在 React 组件中使用 Hooks

直接调用自动生成的 Hooks 发起请求,获取数据和状态:

tsx
// src/components/Users.tsx
import React from 'react';
import { useGetUsersQuery, useAddUserMutation } from '../services/userApi';

const Users = () => {
  // 调用 Query Hook,发起请求
  // 参数:query 的入参(这里 void 类型传 undefined)
  // 返回值:data(响应数据)、isLoading(加载中)、error(错误信息)等
  const { data: users, isLoading, error } = useGetUsersQuery(undefined);
  
  // 调用 Mutation Hook,返回 [触发函数, 状态对象]
  const [addUser, { isLoading: isAdding }] = useAddUserMutation();

  const handleAddUser = async () => {
    try {
      await addUser({ name: 'New User', age: 25 }).unwrap();
      alert('添加成功');
    } catch (err) {
      alert('添加失败');
    }
  };

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

  return (
    <div>
      <h2>用户列表</h2>
      <button onClick={handleAddUser} disabled={isAdding}>
        {isAdding ? '添加中...' : '添加用户'}
      </button>
      <ul>
        {users?.map((user) => (
          <li key={user.id}>{user.name} - {user.age} 岁</li>
        ))}
      </ul>
    </div>
  );
};

export default Users;

四、关键特性详解

1. 缓存控制

(1)缓存有效期

通过 keepUnusedDataFor 配置单个 Query 的缓存时间(单位:秒),默认 60 秒:

typescript
getUsers: builder.query<User[], void>({
  query: () => '/users',
  providesTags: ['User'],
  keepUnusedDataFor: 30, // 30 秒内无组件使用则清除缓存
}),

(2)标签缓存策略

标签分为静态标签和动态标签,用于精准控制缓存失效范围:

  • 静态标签:适用于列表数据,如 providesTags: ['User'],失效时整个列表缓存更新。
  • 动态标签:适用于单条数据,如 providesTags: (result, error, id) => [{ type: 'User', id }],失效时仅更新对应 ID 的数据缓存。

示例:修改用户后,仅失效该用户的缓存

typescript
updateUser: builder.mutation<User, { id: number; data: Partial<User> }>({
  query: ({ id, data }) => ({
    url: `/users/${id}`,
    method: 'PUT',
    body: data,
  }),
  // 仅使 ID 对应的 User 标签失效
  invalidatesTags: (result, error, { id }) => [{ type: 'User', id }],
}),

2. 预加载数据

使用 initiate 方法在组件挂载前预加载数据,适用于路由跳转前的预请求:

typescript
// src/App.tsx
import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { userApi } from './services/userApi';

const App = () => {
  const dispatch = useDispatch();

  useEffect(() => {
    // 预加载用户列表数据
    const promise = dispatch(userApi.endpoints.getUsers.initiate(undefined));
    // 组件卸载时取消请求(可选)
    return () => promise.unsubscribe();
  }, [dispatch]);

  return <Users />;
};

3. 自定义请求逻辑

(1)请求拦截与响应转换

通过 baseQuery 的 prepareHeaders 配置请求头(如 Token),通过 transformResponse 转换响应数据:

typescript
baseQuery: fetchBaseQuery({
  baseUrl: 'https://api.example.com',
  prepareHeaders: (headers, { getState }) => {
    // 从 Redux Store 获取 Token
    const token = (getState() as RootState).auth.token;
    if (token) {
      headers.set('authorization', `Bearer ${token}`);
    }
    return headers;
  },
}),
endpoints: (builder) => ({
  getUsers: builder.query<User[], void>({
    query: () => '/users',
    // 转换响应数据,提取 data 字段
    transformResponse: (response: { data: User[] }) => response.data,
  }),
}),

(2)错误处理

通过 transformErrorResponse 统一处理错误响应:

typescript
getUsers: builder.query<User[], void>({
  query: () => '/users',
  transformErrorResponse: (response) => {
    // 自定义错误信息
    return { message: response.data?.msg || '请求失败' };
  },
}),

4. 与 TypeScript 结合

RTK Query 对 TypeScript 有完善的类型支持,只需定义好请求参数和响应数据的类型,即可获得完整的类型提示:

  • 定义 builder.query<TResult, TArg>:TResult 为响应数据类型,TArg 为请求参数类型。
  • 自动生成的 Hooks 会继承 Endpoint 的类型,避免类型错误。

五、与传统 Redux 异步方案的对比

特性传统 Redux(thunk + 手动 reducer)RTK Query
样板代码量多(需定义 action、reducer 等)少(仅需定义 Endpoint)
缓存管理需手动实现内置自动缓存、失效策略
请求状态(loading)需手动维护内置 isLoading/isError 等状态
数据一致性需手动同步基于标签自动更新缓存
代码复用性低(需封装重复逻辑)高(Endpoint 可跨组件复用)

六、适用场景与注意事项

1. 适用场景

  • 中大型 React + Redux 项目,需要管理大量异步请求。
  • 需要缓存数据、避免重复请求的场景(如列表页、详情页)。
  • 需要统一管理请求状态、简化错误处理的场景。

2. 注意事项

  • RTK Query 依赖 Redux,小型项目(如仅需少量请求)可直接使用 fetch 或 axios。
  • 缓存策略需合理配置,避免因缓存过期导致数据不一致。
  • Mutation 请求触发后,需正确配置 invalidatesTags,否则缓存不会自动更新。
评论
0/100