Redux Toolkit触发Immer修改draft同时返回新值报错排查
Immer 报错根因
你遇到的报错:
An immer producer returned a new value and modified its draft. Either return a new value or modify the draft.
是Redux Toolkit内置的Immer状态更新规则限制:同一个reducer里,你只能选择两种更新模式的其中一种,不能同时使用:
- 直接修改Immer包装的draft状态对象,不手动return新值
- 不修改draft,完全自己构造新的状态对象return
你之前认为filter、map不会修改原始状态的认知,在普通JS场景下是对的,但在createSlice的reducer里不成立:这里传入的state是Immer生成的Proxy代理对象,从state上通过filter、find拿到的子元素也都是代理对象,你执行currentArray[0].votes += 1的时候,已经在直接修改draft状态了;但你后续又自己构造了sortedArray作为返回值,相当于同时触发了两种更新模式,直接触发报错。
代码里的具体问题点
const currentArray = state.filter(...)拿到的数组元素是Immer代理的draft对象,不是普通JS对象,修改它的属性就属于修改draft- 修改draft后又返回自定义的新数组,违反Immer更新规则
- 逻辑问题:
_.sortBy默认是升序排序,票数高的内容会排到数组末尾,不符合投票榜单的常规展示逻辑 - 逻辑bug:
createNotificationreducer直接return action.payload会把整个轶事列表的状态替换成通知内容,通知逻辑应该单独建slice存储,不要和轶事数据放在同一个状态节点下
修复方案
二选一即可,第一种是Redux Toolkit官方推荐的写法,代码更简洁:
方案1:直接修改draft(推荐)
不需要手动构造新数组、不需要return,Immer会自动根据你的修改生成不可变状态:
increaseVote(state, action) { // 找到目标投票对象 const target = state.find(item => item.id === action.payload) if (target) { target.votes += 1 } // 直接在draft上排序,b.votes - a.votes是降序,票数高的在前 state.sort((a, b) => b.votes - a.votes) }
方案2:返回全新状态(不修改draft)
全程不要直接修改从state里拿到的对象属性,所有更新都通过返回新对象实现:
increaseVote(state, action) { // 生成更新后的数组,所有修改都返回新对象,不触碰原draft的属性 const updatedList = state.map(item => { if (item.id === action.payload) { // 用展开运算符生成新对象,不要直接写item.votes +=1 return { ...item, votes: item.votes + 1 } } return item }) // 排序后返回,要降序就加.reverse() return _.sortBy(updatedList, 'votes').reverse() }
内容的提问来源于stack exchange,提问作者SupraCoder
相关产品推荐
相关产品推荐

