Appearance
RTK Query数据获取
概述
RTK Query是Redux Toolkit的数据获取和缓存解决方案,专门用来简化在Redux应用中加载数据的常见情况。它建立在Redux Toolkit的createApi和fetchBaseQuery之上,为React应用提供了强大的数据同步功能。
RTK Query的优势
与传统方式对比
jsx
// 传统方式 - 大量样板代码
const FETCH_USERS_START = 'users/fetchStart';
const FETCH_USERS_SUCCESS = 'users/fetchSuccess';
const FETCH_USERS_ERROR = 'users/fetchError';
// Action creators
const fetchUsersStart = () => ({ type: FETCH_USERS_START });
const fetchUsersSuccess = (users) => ({ type: FETCH_USERS_SUCCESS, payload: users });
const fetchUsersError = (error) => ({ type: FETCH_USERS_ERROR, payload: error });
// Thunk
const fetchUsers = () => async (dispatch) => {
dispatch(fetchUsersStart());
try {
const response = await fetch('/api/users');
const users = await response.json();
dispatch(fetchUsersSuccess(users));
} catch (error) {
dispatch(fetchUsersError(error.message));
}
};
// Reducer
const usersReducer = (state = { data: [], loading: false, error: null }, action) => {
switch (action.type) {
case FETCH_USERS_START:
return { ...state, loading: true, error: null };
case FETCH_USERS_SUCCESS:
return { ...state, loading: false, data: action.payload };
case FETCH_USERS_ERROR:
return { ...state, loading: false, error: action.payload };
default:
return state;
}
};
// RTK Query - 简洁明了
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
const usersApi = createApi({
reducerPath: 'usersApi',
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
endpoints: (builder) => ({
getUsers: builder.query({
query: () => 'users'
})
})
});
export const { useGetUsersQuery } = usersApi;
// 在组件中使用
function UsersList() {
const { data: users, error, isLoading } = useGetUsersQuery();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{users?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}基础设置
安装
bash
# RTK Query 包含在 @reduxjs/toolkit 中
npm install @reduxjs/toolkit react-redux创建API Slice
jsx
// api/apiSlice.js
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: '/api/',
// 可选:添加认证token
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.token;
if (token) {
headers.set('authorization', `Bearer ${token}`);
}
return headers;
}
}),
// 标签用于缓存失效
tagTypes: ['User', 'Post', 'Comment'],
endpoints: (builder) => ({
// endpoints 将在这里定义
})
});Store配置
jsx
// store.js
import { configureStore } from '@reduxjs/toolkit';
import { apiSlice } from './api/apiSlice';
export const store = configureStore({
reducer: {
[apiSlice.reducerPath]: apiSlice.reducer,
// 其他 reducers
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(apiSlice.middleware)
});查询 Endpoints
基础查询
jsx
const apiSlice = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
tagTypes: ['User'],
endpoints: (builder) => ({
// 获取所有用户
getUsers: builder.query({
query: () => 'users',
providesTags: ['User']
}),
// 获取单个用户
getUser: builder.query({
query: (id) => `users/${id}`,
providesTags: (result, error, id) => [{ type: 'User', id }]
}),
// 带参数的查询
getUserPosts: builder.query({
query: ({ userId, page = 1, limit = 10 }) =>
`users/${userId}/posts?page=${page}&limit=${limit}`,
providesTags: (result, error, { userId }) => [
{ type: 'Post', id: 'LIST' },
...result?.map(({ id }) => ({ type: 'Post', id })) || []
]
}),
// 搜索查询
searchUsers: builder.query({
query: (searchTerm) => `users/search?q=${encodeURIComponent(searchTerm)}`,
// 动态标签
providesTags: (result) =>
result?.map(({ id }) => ({ type: 'User', id })) || ['User']
})
})
});
export const {
useGetUsersQuery,
useGetUserQuery,
useGetUserPostsQuery,
useSearchUsersQuery
} = apiSlice;使用查询Hooks
jsx
function UserProfile({ userId }) {
const {
data: user,
error,
isLoading,
isSuccess,
isError,
refetch
} = useGetUserQuery(userId);
const {
data: posts,
isLoading: postsLoading
} = useGetUserPostsQuery({ userId, page: 1 });
if (isLoading) return <div>Loading user...</div>;
if (isError) return <div>Error: {error.message}</div>;
if (!user) return <div>User not found</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
<button onClick={refetch}>Refresh</button>
<h2>Posts</h2>
{postsLoading ? (
<div>Loading posts...</div>
) : (
<ul>
{posts?.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)}
</div>
);
}查询选项
jsx
function UsersList() {
const {
data: users,
error,
isLoading
} = useGetUsersQuery(undefined, {
// 轮询间隔(毫秒)
pollingInterval: 30000,
// 窗口获得焦点时重新获取
refetchOnFocus: true,
// 重新连接时重新获取
refetchOnReconnect: true,
// 挂载时重新获取
refetchOnMountOrArgChange: true,
// 跳过查询
skip: false,
// 选择数据
selectFromResult: ({ data, ...other }) => ({
data: data?.filter(user => user.active),
...other
})
});
// ...
}变更 Endpoints
基础变更
jsx
const apiSlice = createApi({
// ... 配置
endpoints: (builder) => ({
// 创建用户
createUser: builder.mutation({
query: (newUser) => ({
url: 'users',
method: 'POST',
body: newUser
}),
invalidatesTags: ['User']
}),
// 更新用户
updateUser: builder.mutation({
query: ({ id, ...updates }) => ({
url: `users/${id}`,
method: 'PATCH',
body: updates
}),
invalidatesTags: (result, error, { id }) => [{ type: 'User', id }]
}),
// 删除用户
deleteUser: builder.mutation({
query: (id) => ({
url: `users/${id}`,
method: 'DELETE'
}),
invalidatesTags: (result, error, id) => [{ type: 'User', id }]
}),
// 上传头像
uploadAvatar: builder.mutation({
query: ({ userId, file }) => {
const formData = new FormData();
formData.append('avatar', file);
return {
url: `users/${userId}/avatar`,
method: 'POST',
body: formData,
formData: true
};
},
invalidatesTags: (result, error, { userId }) => [{ type: 'User', id: userId }]
})
})
});
export const {
useCreateUserMutation,
useUpdateUserMutation,
useDeleteUserMutation,
useUploadAvatarMutation
} = apiSlice;使用变更Hooks
jsx
function UserForm({ user, onSuccess }) {
const [name, setName] = useState(user?.name || '');
const [email, setEmail] = useState(user?.email || '');
const [createUser, {
isLoading: isCreating,
error: createError
}] = useCreateUserMutation();
const [updateUser, {
isLoading: isUpdating,
error: updateError
}] = useUpdateUserMutation();
const isLoading = isCreating || isUpdating;
const error = createError || updateError;
const handleSubmit = async (e) => {
e.preventDefault();
try {
const userData = { name, email };
if (user) {
await updateUser({ id: user.id, ...userData }).unwrap();
} else {
await createUser(userData).unwrap();
}
onSuccess?.();
} catch (err) {
console.error('Failed to save user:', err);
}
};
return (
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
required
/>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Saving...' : user ? 'Update' : 'Create'}
</button>
{error && <div className="error">{error.message}</div>}
</form>
);
}乐观更新
jsx
const apiSlice = createApi({
// ... 配置
endpoints: (builder) => ({
updateUser: builder.mutation({
query: ({ id, ...updates }) => ({
url: `users/${id}`,
method: 'PATCH',
body: updates
}),
// 乐观更新
onQueryStarted: async ({ id, ...updates }, { dispatch, queryFulfilled }) => {
// 乐观更新缓存
const patchResult = dispatch(
apiSlice.util.updateQueryData('getUser', id, (draft) => {
Object.assign(draft, updates);
})
);
try {
await queryFulfilled;
} catch {
// 失败时回滚
patchResult.undo();
}
},
invalidatesTags: (result, error, { id }) => [{ type: 'User', id }]
}),
toggleUserStatus: builder.mutation({
query: ({ id, active }) => ({
url: `users/${id}/status`,
method: 'PATCH',
body: { active }
}),
onQueryStarted: async ({ id, active }, { dispatch, queryFulfilled }) => {
// 更新单个用户缓存
const userPatch = dispatch(
apiSlice.util.updateQueryData('getUser', id, (draft) => {
draft.active = active;
})
);
// 更新用户列表缓存
const usersPatch = dispatch(
apiSlice.util.updateQueryData('getUsers', undefined, (draft) => {
const user = draft.find(user => user.id === id);
if (user) {
user.active = active;
}
})
);
try {
await queryFulfilled;
} catch {
userPatch.undo();
usersPatch.undo();
}
}
})
})
});缓存管理
标签系统
jsx
const apiSlice = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
tagTypes: ['User', 'Post', 'Comment', 'Like'],
endpoints: (builder) => ({
// 查询提供标签
getUsers: builder.query({
query: () => 'users',
providesTags: (result) =>
result
? [
...result.map(({ id }) => ({ type: 'User', id })),
{ type: 'User', id: 'LIST' }
]
: [{ type: 'User', id: 'LIST' }]
}),
getPost: builder.query({
query: (id) => `posts/${id}`,
providesTags: (result, error, id) => [{ type: 'Post', id }]
}),
getPostComments: builder.query({
query: (postId) => `posts/${postId}/comments`,
providesTags: (result, error, postId) =>
result
? [
...result.map(({ id }) => ({ type: 'Comment', id })),
{ type: 'Comment', id: 'LIST' }
]
: [{ type: 'Comment', id: 'LIST' }]
}),
// 变更使缓存失效
createUser: builder.mutation({
query: (newUser) => ({
url: 'users',
method: 'POST',
body: newUser
}),
invalidatesTags: [{ type: 'User', id: 'LIST' }]
}),
updateUser: builder.mutation({
query: ({ id, ...updates }) => ({
url: `users/${id}`,
method: 'PATCH',
body: updates
}),
invalidatesTags: (result, error, { id }) => [{ type: 'User', id }]
}),
deletePost: builder.mutation({
query: (id) => ({
url: `posts/${id}`,
method: 'DELETE'
}),
invalidatesTags: (result, error, id) => [
{ type: 'Post', id },
{ type: 'Post', id: 'LIST' },
{ type: 'Comment', id: 'LIST' } // 删除帖子时也清除评论缓存
]
})
})
});手动缓存管理
jsx
function PostActions({ postId }) {
const dispatch = useDispatch();
const handleRefreshPost = () => {
// 手动刷新特定查询
dispatch(
apiSlice.util.invalidateTags([{ type: 'Post', id: postId }])
);
};
const handlePrefetchComments = () => {
// 预取数据
dispatch(
apiSlice.util.prefetch('getPostComments', postId, { force: true })
);
};
const handleUpdateCache = (newData) => {
// 直接更新缓存
dispatch(
apiSlice.util.updateQueryData('getPost', postId, (draft) => {
Object.assign(draft, newData);
})
);
};
return (
<div>
<button onClick={handleRefreshPost}>Refresh</button>
<button onClick={handlePrefetchComments}>Prefetch Comments</button>
</div>
);
}选择性缓存失效
jsx
const apiSlice = createApi({
// ... 配置
endpoints: (builder) => ({
likePost: builder.mutation({
query: ({ postId, userId }) => ({
url: `posts/${postId}/like`,
method: 'POST',
body: { userId }
}),
onQueryStarted: async ({ postId, userId }, { dispatch, queryFulfilled }) => {
// 乐观更新帖子的点赞数
const postPatch = dispatch(
apiSlice.util.updateQueryData('getPost', postId, (draft) => {
draft.likes = (draft.likes || 0) + 1;
draft.likedByCurrentUser = true;
})
);
// 更新帖子列表中的点赞数
const postsPatch = dispatch(
apiSlice.util.updateQueryData('getPosts', undefined, (draft) => {
const post = draft.find(p => p.id === postId);
if (post) {
post.likes = (post.likes || 0) + 1;
post.likedByCurrentUser = true;
}
})
);
try {
await queryFulfilled;
} catch {
postPatch.undo();
postsPatch.undo();
}
}
})
})
});高级特性
自定义 BaseQuery
jsx
import { fetchBaseQuery, retry } from '@reduxjs/toolkit/query/react';
// 带重试的 BaseQuery
const baseQueryWithRetry = retry(
fetchBaseQuery({
baseUrl: '/api/',
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.token;
if (token) {
headers.set('authorization', `Bearer ${token}`);
}
return headers;
}
}),
{ maxRetries: 3 }
);
// 带认证刷新的 BaseQuery
const baseQueryWithReauth = async (args, api, extraOptions) => {
let result = await fetchBaseQuery({
baseUrl: '/api/',
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.token;
if (token) {
headers.set('authorization', `Bearer ${token}`);
}
return headers;
}
})(args, api, extraOptions);
if (result.error && result.error.status === 401) {
// 尝试刷新token
const refreshResult = await fetchBaseQuery({
baseUrl: '/api/'
})('/auth/refresh', api, extraOptions);
if (refreshResult.data) {
api.dispatch(setToken(refreshResult.data.token));
// 重新执行原始查询
result = await fetchBaseQuery({
baseUrl: '/api/',
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.token;
if (token) {
headers.set('authorization', `Bearer ${token}`);
}
return headers;
}
})(args, api, extraOptions);
} else {
api.dispatch(logout());
}
}
return result;
};
const apiSlice = createApi({
reducerPath: 'api',
baseQuery: baseQueryWithReauth,
// ... 其他配置
});条件查询
jsx
function UserProfile({ userId, shouldFetch }) {
const {
data: user,
error,
isLoading
} = useGetUserQuery(userId, {
// 条件性跳过查询
skip: !userId || !shouldFetch
});
// 或使用 skipToken
const {
data: posts
} = useGetUserPostsQuery(userId || skipToken);
// ...
}变换响应数据
jsx
const apiSlice = createApi({
// ... 配置
endpoints: (builder) => ({
getUsers: builder.query({
query: () => 'users',
// 变换响应数据
transformResponse: (responseData) => {
// 规范化数据
return responseData.map(user => ({
id: user.id,
name: user.name,
email: user.email,
displayName: `${user.name} <${user.email}>`,
initials: user.name.split(' ').map(n => n[0]).join('')
}));
}
}),
getUserStats: builder.query({
query: (userId) => `users/${userId}/stats`,
transformResponse: (response) => ({
...response,
// 计算派生数据
engagementRate: response.likes / response.posts * 100,
averagePostsPerMonth: response.posts / response.monthsActive
})
})
})
});错误处理
jsx
const apiSlice = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: '/api/',
// 自定义错误处理
responseHandler: async (response) => {
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Something went wrong');
}
return response.json();
}
}),
endpoints: (builder) => ({
createUser: builder.mutation({
query: (userData) => ({
url: 'users',
method: 'POST',
body: userData
}),
// 变换错误
transformErrorResponse: (response) => {
if (response.status === 422) {
return {
message: 'Validation failed',
errors: response.data.errors
};
}
return response.data;
}
})
})
});
// 在组件中处理错误
function CreateUserForm() {
const [createUser, { error, isLoading }] = useCreateUserMutation();
const handleSubmit = async (userData) => {
try {
await createUser(userData).unwrap();
// 成功处理
} catch (err) {
// 错误已经在 error 状态中
console.error('Failed to create user:', err);
}
};
return (
<form onSubmit={handleSubmit}>
{/* 表单字段 */}
{error && (
<div className="error">
{error.message}
{error.errors && (
<ul>
{Object.entries(error.errors).map(([field, messages]) => (
<li key={field}>{field}: {messages.join(', ')}</li>
))}
</ul>
)}
</div>
)}
<button type="submit" disabled={isLoading}>
Create User
</button>
</form>
);
}实战案例
案例1:博客应用
jsx
// api/blogApi.js
const blogApi = createApi({
reducerPath: 'blogApi',
baseQuery: fetchBaseQuery({ baseUrl: '/api/blog/' }),
tagTypes: ['Post', 'User', 'Comment', 'Tag'],
endpoints: (builder) => ({
// 帖子相关
getPosts: builder.query({
query: ({ page = 1, limit = 10, tag, author } = {}) => {
const params = new URLSearchParams({
page: page.toString(),
limit: limit.toString()
});
if (tag) params.append('tag', tag);
if (author) params.append('author', author);
return `posts?${params}`;
},
providesTags: (result) =>
result
? [
...result.data.map(({ id }) => ({ type: 'Post', id })),
{ type: 'Post', id: 'PARTIAL-LIST' }
]
: [{ type: 'Post', id: 'PARTIAL-LIST' }],
// 合并分页数据
serializeQueryArgs: ({ queryArgs }) => {
const { page, ...otherArgs } = queryArgs;
return otherArgs;
},
merge: (currentCache, newItems, { arg }) => {
if (arg.page === 1) {
currentCache.data = newItems.data;
} else {
currentCache.data.push(...newItems.data);
}
currentCache.pagination = newItems.pagination;
},
forceRefetch({ currentArg, previousArg }) {
return currentArg?.page !== previousArg?.page;
}
}),
getPost: builder.query({
query: (id) => `posts/${id}`,
providesTags: (result, error, id) => [{ type: 'Post', id }]
}),
createPost: builder.mutation({
query: (newPost) => ({
url: 'posts',
method: 'POST',
body: newPost
}),
invalidatesTags: [{ type: 'Post', id: 'PARTIAL-LIST' }]
}),
updatePost: builder.mutation({
query: ({ id, ...updates }) => ({
url: `posts/${id}`,
method: 'PATCH',
body: updates
}),
invalidatesTags: (result, error, { id }) => [{ type: 'Post', id }]
}),
deletePost: builder.mutation({
query: (id) => ({
url: `posts/${id}`,
method: 'DELETE'
}),
invalidatesTags: (result, error, id) => [
{ type: 'Post', id },
{ type: 'Post', id: 'PARTIAL-LIST' }
]
}),
// 评论相关
getPostComments: builder.query({
query: (postId) => `posts/${postId}/comments`,
providesTags: (result, error, postId) =>
result
? [
...result.map(({ id }) => ({ type: 'Comment', id })),
{ type: 'Comment', id: `LIST-${postId}` }
]
: [{ type: 'Comment', id: `LIST-${postId}` }]
}),
addComment: builder.mutation({
query: ({ postId, content }) => ({
url: `posts/${postId}/comments`,
method: 'POST',
body: { content }
}),
invalidatesTags: (result, error, { postId }) => [
{ type: 'Comment', id: `LIST-${postId}` }
]
}),
// 标签相关
getTags: builder.query({
query: () => 'tags',
providesTags: ['Tag']
}),
// 搜索
searchPosts: builder.query({
query: (searchTerm) => `search?q=${encodeURIComponent(searchTerm)}`,
providesTags: ['Post']
})
})
});
export const {
useGetPostsQuery,
useGetPostQuery,
useCreatePostMutation,
useUpdatePostMutation,
useDeletePostMutation,
useGetPostCommentsQuery,
useAddCommentMutation,
useGetTagsQuery,
useSearchPostsQuery
} = blogApi;
export default blogApi;案例2:电商应用
jsx
// api/ecommerceApi.js
const ecommerceApi = createApi({
reducerPath: 'ecommerceApi',
baseQuery: fetchBaseQuery({
baseUrl: '/api/ecommerce/',
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.token;
if (token) {
headers.set('authorization', `Bearer ${token}`);
}
return headers;
}
}),
tagTypes: ['Product', 'Category', 'Cart', 'Order', 'User'],
endpoints: (builder) => ({
// 产品相关
getProducts: builder.query({
query: ({
category,
minPrice,
maxPrice,
sortBy = 'name',
order = 'asc',
page = 1,
limit = 20
} = {}) => {
const params = new URLSearchParams({
page: page.toString(),
limit: limit.toString(),
sortBy,
order
});
if (category) params.append('category', category);
if (minPrice) params.append('minPrice', minPrice.toString());
if (maxPrice) params.append('maxPrice', maxPrice.toString());
return `products?${params}`;
},
providesTags: (result) =>
result
? [
...result.data.map(({ id }) => ({ type: 'Product', id })),
{ type: 'Product', id: 'LIST' }
]
: [{ type: 'Product', id: 'LIST' }]
}),
getProduct: builder.query({
query: (id) => `products/${id}`,
providesTags: (result, error, id) => [{ type: 'Product', id }]
}),
// 购物车相关
getCart: builder.query({
query: () => 'cart',
providesTags: ['Cart']
}),
addToCart: builder.mutation({
query: ({ productId, quantity = 1, options = {} }) => ({
url: 'cart/items',
method: 'POST',
body: { productId, quantity, options }
}),
// 乐观更新
onQueryStarted: async ({ productId, quantity, options }, { dispatch, queryFulfilled, getState }) => {
const patchResult = dispatch(
ecommerceApi.util.updateQueryData('getCart', undefined, (draft) => {
const existingItem = draft.items.find(
item => item.productId === productId &&
JSON.stringify(item.options) === JSON.stringify(options)
);
if (existingItem) {
existingItem.quantity += quantity;
} else {
// 从产品缓存中获取产品信息
const product = ecommerceApi.endpoints.getProduct.select(productId)(getState())?.data;
if (product) {
draft.items.push({
id: `${productId}_${JSON.stringify(options)}_${Date.now()}`,
productId,
product,
quantity,
options
});
}
}
// 重新计算总价
draft.total = draft.items.reduce(
(sum, item) => sum + (item.product.price * item.quantity),
0
);
})
);
try {
await queryFulfilled;
} catch {
patchResult.undo();
}
},
invalidatesTags: ['Cart']
}),
updateCartItem: builder.mutation({
query: ({ itemId, quantity }) => ({
url: `cart/items/${itemId}`,
method: 'PATCH',
body: { quantity }
}),
onQueryStarted: async ({ itemId, quantity }, { dispatch, queryFulfilled }) => {
const patchResult = dispatch(
ecommerceApi.util.updateQueryData('getCart', undefined, (draft) => {
const item = draft.items.find(item => item.id === itemId);
if (item) {
if (quantity <= 0) {
draft.items = draft.items.filter(item => item.id !== itemId);
} else {
item.quantity = quantity;
}
draft.total = draft.items.reduce(
(sum, item) => sum + (item.product.price * item.quantity),
0
);
}
})
);
try {
await queryFulfilled;
} catch {
patchResult.undo();
}
},
invalidatesTags: ['Cart']
}),
removeFromCart: builder.mutation({
query: (itemId) => ({
url: `cart/items/${itemId}`,
method: 'DELETE'
}),
invalidatesTags: ['Cart']
}),
clearCart: builder.mutation({
query: () => ({
url: 'cart',
method: 'DELETE'
}),
invalidatesTags: ['Cart']
}),
// 订单相关
createOrder: builder.mutation({
query: (orderData) => ({
url: 'orders',
method: 'POST',
body: orderData
}),
invalidatesTags: ['Order', 'Cart']
}),
getOrders: builder.query({
query: () => 'orders',
providesTags: (result) =>
result
? [
...result.map(({ id }) => ({ type: 'Order', id })),
{ type: 'Order', id: 'LIST' }
]
: [{ type: 'Order', id: 'LIST' }]
}),
getOrder: builder.query({
query: (id) => `orders/${id}`,
providesTags: (result, error, id) => [{ type: 'Order', id }]
}),
// 分类相关
getCategories: builder.query({
query: () => 'categories',
providesTags: ['Category']
})
})
});
export const {
useGetProductsQuery,
useGetProductQuery,
useGetCartQuery,
useAddToCartMutation,
useUpdateCartItemMutation,
useRemoveFromCartMutation,
useClearCartMutation,
useCreateOrderMutation,
useGetOrdersQuery,
useGetOrderQuery,
useGetCategoriesQuery
} = ecommerceApi;
export default ecommerceApi;最佳实践
1. API 设计
jsx
// 好的API设计
const goodApi = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
tagTypes: ['User', 'Post'], // 明确定义标签类型
endpoints: (builder) => ({
// 明确的命名
getUsers: builder.query({
query: () => 'users',
providesTags: ['User']
}),
// 带参数的查询
getUser: builder.query({
query: (id) => `users/${id}`,
providesTags: (result, error, id) => [{ type: 'User', id }]
}),
// 描述性的mutation名称
createUser: builder.mutation({
query: (newUser) => ({
url: 'users',
method: 'POST',
body: newUser
}),
invalidatesTags: ['User']
})
})
});
// 避免的设计
const badApi = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
endpoints: (builder) => ({
// 不明确的命名
data: builder.query({
query: () => 'users'
}),
// 缺少缓存标签
update: builder.mutation({
query: (data) => ({
url: 'users',
method: 'POST',
body: data
})
// 没有 invalidatesTags
})
})
});2. 缓存策略
jsx
// 细粒度缓存控制
const apiSlice = createApi({
// ...
endpoints: (builder) => ({
getUsers: builder.query({
query: () => 'users',
// 为列表和单个项目提供不同的标签
providesTags: (result) =>
result
? [
{ type: 'User', id: 'LIST' },
...result.map(({ id }) => ({ type: 'User', id }))
]
: [{ type: 'User', id: 'LIST' }]
}),
createUser: builder.mutation({
query: (newUser) => ({
url: 'users',
method: 'POST',
body: newUser
}),
// 只失效列表,不失效单个用户
invalidatesTags: [{ type: 'User', id: 'LIST' }]
}),
updateUser: builder.mutation({
query: ({ id, ...updates }) => ({
url: `users/${id}`,
method: 'PATCH',
body: updates
}),
// 失效特定用户
invalidatesTags: (result, error, { id }) => [{ type: 'User', id }]
})
})
});3. 错误处理
jsx
// 统一错误处理
const baseQueryWithErrorHandling = fetchBaseQuery({
baseUrl: '/api/',
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.token;
if (token) {
headers.set('authorization', `Bearer ${token}`);
}
return headers;
}
});
const apiSliceWithErrorHandling = async (args, api, extraOptions) => {
const result = await baseQueryWithErrorHandling(args, api, extraOptions);
if (result.error) {
// 全局错误处理
if (result.error.status === 401) {
// 未授权,重定向到登录
api.dispatch(logout());
} else if (result.error.status === 500) {
// 服务器错误,显示通用错误消息
api.dispatch(showErrorNotification('Server error occurred'));
}
}
return result;
};4. TypeScript 类型
typescript
// 定义API响应类型
interface User {
id: number;
name: string;
email: string;
createdAt: string;
}
interface CreateUserRequest {
name: string;
email: string;
}
interface UpdateUserRequest {
id: number;
name?: string;
email?: string;
}
const typedApi = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
tagTypes: ['User'],
endpoints: (builder) => ({
getUsers: builder.query<User[], void>({
query: () => 'users',
providesTags: ['User']
}),
getUser: builder.query<User, number>({
query: (id) => `users/${id}`,
providesTags: (result, error, id) => [{ type: 'User', id }]
}),
createUser: builder.mutation<User, CreateUserRequest>({
query: (newUser) => ({
url: 'users',
method: 'POST',
body: newUser
}),
invalidatesTags: ['User']
}),
updateUser: builder.mutation<User, UpdateUserRequest>({
query: ({ id, ...updates }) => ({
url: `users/${id}`,
method: 'PATCH',
body: updates
}),
invalidatesTags: (result, error, { id }) => [{ type: 'User', id }]
})
})
});总结
RTK Query 是现代 React 应用数据获取的强大解决方案:
- 简化数据获取:自动处理loading、error状态
- 智能缓存:基于标签的缓存失效系统
- 乐观更新:提升用户体验
- TypeScript支持:完整的类型安全
- 灵活配置:支持各种自定义需求
- 性能优化:自动去重、缓存等优化
RTK Query 极大简化了 Redux 应用中的数据管理,是现代 React 开发的最佳选择。