Redux Toolkit购物车开发报错:Immer生产者同时返回新值并修改草稿
问题:Redux Toolkit购物车功能开发中Immer报错
使用Redux Toolkit开发购物车时,尝试通过商品ID修改购物车项索引,触发Immer报错:
[Immer] An immer producer returned a new value and modified its draft. Either return a new value or modify the draft.
怀疑问题出在代码const cartItems = state.cartItems这一行,完整代码及action.payload示例如下:
代码示例
import { createSlice, current } from '@reduxjs/toolkit'; const initialState = { cartItems: [], }; export const cartSlice = createSlice({ name: 'cartSlice', initialState: initialState, reducers: { setToCart: (state, action) => { const { payload:product, newQty = 1 } = action; const cartItems = state.cartItems; const quantity = cartItems?.[product?._id] ? parseInt(cartItems[product._id].quantity + newQty) : 1; cartItems[product._id] = { ...product, quantity, }; return { ...state, cartItems: cartItems } }, }, }); export const {setToCart} = cartSlice.actions; export default cartSlice.reducer;
action.payload示例
{ "img": "_OCT2HGtyHbioAuVTMGcA-mauntain.jpg", "name": "iphone", "price": 600001, "_id": "60d375ed3224711bc0f3538a" }
原因分析
报错核心是同时修改了Immer的draft对象,又返回了新的状态对象,违反Immer规则:要么直接修改draft(Redux Toolkit推荐方式),要么返回全新状态对象,两者不能同时进行。
你的代码中,cartItems = state.cartItems直接引用了draft内的数组,修改cartItems[product._id]属于修改draft操作;但最后又返回{...state, cartItems: cartItems},相当于返回新状态对象,触发Immer检测报错。
另外注意:初始状态中cartItems是数组类型,你却用字符串ID作为索引操作,这不符合数组的使用逻辑,建议改为对象类型更合理。
解决方案
方案1:直接修改draft(Redux Toolkit推荐写法)
无需返回新对象,直接修改state内的cartItems(先将cartItems改为对象类型):
import { createSlice } from '@reduxjs/toolkit'; // 将cartItems改为对象,适配商品ID作为索引的场景 const initialState = { cartItems: {}, }; export const cartSlice = createSlice({ name: 'cartSlice', initialState, reducers: { setToCart: (state, action) => { const { payload: product, newQty = 1 } = action; const existingItem = state.cartItems[product._id]; if (existingItem) { // 直接修改draft中的属性 existingItem.quantity = parseInt(existingItem.quantity) + newQty; } else { // 直接向draft的cartItems添加新项 state.cartItems[product._id] = { ...product, quantity: 1 }; } // 无需返回值,Immer自动处理draft生成不可变状态 }, }, }); export const { setToCart } = cartSlice.actions; export default cartSlice.reducer;
方案2:返回全新状态对象(不推荐,但可解决报错)
若坚持返回新对象,需完全不修改原draft,基于原状态创建新对象:
import { createSlice } from '@reduxjs/toolkit'; const initialState = { cartItems: {}, }; export const cartSlice = createSlice({ name: 'cartSlice', initialState, reducers: { setToCart: (state, action) => { const { payload: product, newQty = 1 } = action; const existingItem = state.cartItems[product._id]; // 创建新的cartItems对象,不修改原draft const newCartItems = { ...state.cartItems, [product._id]: existingItem ? { ...existingItem, quantity: parseInt(existingItem.quantity) + newQty } : { ...product, quantity: 1 } }; // 返回全新状态对象 return { ...state, cartItems: newCartItems }; }, }, }); export const { setToCart } = cartSlice.actions; export default cartSlice.reducer;
额外说明
- 数组使用字符串ID作为索引会导致数组长度异常,用对象存储cartItems(键为商品ID,值为购物车项)更符合业务逻辑。
- Redux Toolkit的createSlice底层基于Immer实现,推荐直接修改draft的写法,代码简洁且Immer会自动处理不可变状态更新。
内容的提问来源于stack exchange,提问作者Kamlesh Yadav
相关产品推荐
相关产品推荐

