如何在createAsyncThunk中正确使用RTK Query Hook
你的写法存在根本性错误,报错是必然的
核心问题有两点:
- RTK Query 生成的
useGetAllCartItemsQuery这类use开头的方法是React Hook,严格遵守React Hook调用规则:只能在React函数组件、自定义Hook的顶层调用,不能在普通JS函数、createAsyncThunk回调这类非React组件上下文里调用,直接调用会直接抛出Hook规则错误。 createAsyncThunk的第一个参数要求传入字符串格式的action type前缀,你直接传入Hook的执行结果作为第一个参数,从API入参规范上就不成立,语法校验阶段就会报错。
正确实现方案
首先明确:RTK Query本身已经内置了请求缓存、加载状态(isLoading/isFetching)、错误状态、数据存储能力,你额外手写createAsyncThunk+slice存储购物车数据、维护加载/错误状态属于重复造轮子,绝大多数场景不需要这么做。
如果确实需要监听购物车请求的生命周期做自定义逻辑,选以下任意一种方案即可:
方案1(推荐):用RTK Query原生生命周期回调,完全不需要手写thunk
在定义cart相关API端点的时候,直接在端点配置里写生命周期逻辑,不需要额外写thunk和重复的状态存储:
// 你的RTK Query API定义文件 import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import { cartSlice } from './cartSlice' export const cartApi = createApi({ reducerPath: 'cartApi', baseQuery: fetchBaseQuery({ baseUrl: '/your-api-base-path' }), endpoints: (builder) => ({ getAllCartItems: builder.query({ // 请求配置,接收传入的token参数 query: (userToken) => ({ url: '/cart/items', headers: { Authorization: `Bearer ${userToken}` } }), // 请求生命周期回调 async onQueryStarted(userToken, { dispatch, queryFulfilled }) { // 请求发起(pending阶段) dispatch(cartSlice.actions.setStatus('loading')) try { const { data } = await queryFulfilled // 请求成功(fulfilled阶段) dispatch(cartSlice.actions.setCartItems(data)) dispatch(cartSlice.actions.setStatus('succeeded')) } catch (err) { // 请求失败(rejected阶段) dispatch(cartSlice.actions.setError(err.error.message)) dispatch(cartSlice.actions.setStatus('failed')) } } }) }) }) // 导出组件用的Hook export const { useGetAllCartItemsQuery } = cartApi
组件里直接用生成的Hook就行,不需要额外调用thunk:
// 组件内使用 function Cart() { const { data: cartItems, isLoading, error } = useGetAllCartItemsQuery(userLToken) // 直接用返回的状态和数据渲染即可 }
方案2:必须手写thunk的场景,不要调用Hook,用initiate方法触发请求
如果你有特殊逻辑必须放在thunk里处理,不要用use开头的Hook,调用RTK Query端点暴露的initiate普通方法即可,这个方法不依赖React上下文,可以在任意JS逻辑里调用:
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' import { cartApi } from './cartApi' // 正确的thunk写法 export const getCartItems = createAsyncThunk( 'cart/getCartItems', // 第一个参数固定为字符串action前缀 async (userToken, { dispatch }) => { // 用initiate触发请求,unwrap()直接解析返回的业务数据 const cartData = await dispatch( cartApi.endpoints.getAllCartItems.initiate(userToken) ).unwrap() return cartData } ) const cartSlice = createSlice({ name: 'cart', initialState: { itemsList: [], status: 'idle', error: null }, reducers: { // 其他同步reducer }, extraReducers: (builder) => { builder .addCase(getCartItems.pending, (state) => { state.status = "loading" }) .addCase(getCartItems.fulfilled, (state, action) => { state.status = "succeeded" // 注意:这里要直接替换数组,不要用push,否则每次请求都会重复追加数据 state.itemsList = action.payload }) .addCase(getCartItems.rejected, (state, action) => { state.status = "failed" state.error = action.error.message; }) } })
注意:你原来的fulfilled回调里用
state.itemsList.push(action.payload)是逻辑错误,action.payload是整个购物车商品数组,push会把整个数组作为单个元素追加到列表里,多次请求会出现数据重复。
内容的提问来源于stack exchange,提问作者Raghuram
相关产品推荐
相关产品推荐

