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

为何对局部变量使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 14:57:32