为何对局部变量使用sort()会触发Redux状态突变检测错误?
问题描述
我正在学习用Redux Toolkit的createSlice()展示可预订数据,在BookableList.js里对局部变量bookable(非状态)调用sort()时,触发了Redux的状态突变检测错误:
A state mutation was detected between dispatches, in the path
'bookables.0.days.0'.
出错的代码片段:
bookable.days.sort().map((d, i) => ( <li key={i}>{days[d]}</li> ))}
替换成toSorted()后问题解决,但我不明白原因——明明是对局部变量操作,为什么会影响Redux状态?
完整代码
BookableList.js
import React, { Fragment } from 'react'; import { sessions, days } from '../../static.json'; import { useSelector, useDispatch } from 'react-redux'; import { getNextBookable } from './BookablesSlice'; import { FaArrowRight } from 'react-icons/fa'; export const BookablesList = () => { const dispatch = useDispatch(); const { group, bookableIndex, hasDetails, bookables } = useSelector( state => state ); const bookablesInGroup = bookables.filter((b) => b.group === group); const groups = [...new Set(bookables.map((b) => b.group))]; const bookable = bookablesInGroup[bookableIndex]; const nextBookableHandler = () => { dispatch(getNextBookable()); }; // ... return ( <Fragment> <div> <select onChange={changeGroup}> {groups.map((name, i) => ( <option key={i}>{name}</option> ))} </select> <ul className='bookables items-list-nav'> {bookablesInGroup.map((b, i) => ( <li key={i} className={i === bookableIndex ? "selected" : null}> <button className='btn' onClick={changeBookable}> {b.title} </button> </li> ))} </ul> <p> <button className='btn' onClick={nextBookableHandler} autoFocus > <FaArrowRight> <span>Next</span> </FaArrowRight> </button> </p> </div> {bookable && ( <div className='bookable-details'> <div className='item'> <div className='item-header'> <h2>{bookable.title}</h2> <span className='controls'> <input type='checkbox' id='hasDetails' name='hasDetails' checked={hasDetails} onChange={toggleHasDetails} /> <label htmlFor='showDetails'>Show Details</label> </span> </div> <p>{bookable.notes}</p> {hasDetails && ( <div className='item-details'> <h3>Availability</h3> <div className='bookable-availability'> <ul> { // Replaced sort() with toSorted() function to avoid a mutation during render. bookable.days.sort().map((d, i) => ( <li key={i}>{days[d]}</li> ))} </ul> <ul> {bookable.sessions.toSorted().map((s, i) => ( <li key={i}>{sessions[s]}</li> ))} </ul> </div> </div> )} </div> </div> )} </Fragment> ); };
BookablesSlice.js
import { createSlice } from '@reduxjs/toolkit'; export const bookablesSlice = createSlice({ name: 'nextBookable', initialState: {}, reducers: { getNextBookable: (state, action) => { const count = state.bookables.filter(b => b.group === state.group).length; return { ...state, bookableIndex: (state.bookableIndex + 1) % count, } }, } }) // this is for dispatch export const { getNextBookable } = bookablesSlice.actions; //this is for configureStore export default bookablesSlice.reducer;
store.js
import { configureStore } from '@reduxjs/toolkit'; import bookableReducer from './components/Bookables/BookablesSlice'; import { bookables } from './static.json'; const initialState = { group: "Rooms", bookableIndex: 0, hasDetails: true, bookables, }; const store = configureStore({ reducer: bookableReducer, preloadedState: initialState, }); export default store;
原因解释
核心问题出在JavaScript引用类型的特性上:
- 你通过
const bookable = bookablesInGroup[bookableIndex]拿到的局部变量,本质上是Redux状态中bookables数组里对应对象的引用,不是全新的副本。 Array.sort()是原地排序方法,会直接修改原数组的内容。当你调用bookable.days.sort()时,实际上是在修改Redux状态里bookables[X].days这个数组本身,这就触发了Redux的状态突变检测。
而toSorted()是ES2023新增的方法,它会返回一个排序后的新数组,不会修改原数组。所以用bookable.days.toSorted()时,原Redux状态里的数组完全没被改动,自然不会触发突变错误。
兼容方案
如果需要兼容不支持toSorted()的环境,也可以先复制原数组再排序:
[...bookable.days].sort().map(...) // 或者 bookable.days.slice().sort().map(...)
内容的提问来源于stack exchange,提问作者resant
相关产品推荐
相关产品推荐

