You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 02:55:17