React/Redux Toolkit中编辑activeBoard列数组报错求助
问题描述
使用Redux Toolkit开发任务管理器时,从board切片获取activeBoard后,编辑页面中boardName输入框可正常修改,但修改activeBoard.columns数组内的文本时,持续报错:
"Cannot assign to read only property 'board' of object"
已尝试将activeBoard.columns传入useState作为本地状态,但仍报错;若硬编码数组到useState则可正常编辑列输入框。
EditBoardModal.jsx
import React, {useState} from 'react'; import {useDispatch, useSelector} from "react-redux"; import {editBoard} from "../../reducers/board/boardSlice"; import {hideModal} from "../../reducers/modal/modalSlice"; import Button from "../Button/Button"; import "./EditBoardModal.scss"; const EditBoardModal = () => { const dispatch = useDispatch(); const {activeBoard} = useSelector(store => store.board); const [boardName, setBoardName] = useState(activeBoard.name); const [columns, setColumns] = useState([...activeBoard.columns]); const nameChangeHandler = (e) => { setBoardName(e.target.value) } const columnsChangeHandler = (i, e) => { let columnsValues = [...columns]; columnsValues[i][e.target.name] = e.target.value; console.log(columnsValues[0][e.target.name]); setColumns(columnsValues); } return ( <div className="edit-board-modal"> <h3 className="edit-modal-title">Edit Board</h3> <div className="edit-board-name-div"> <label>Board Name</label> <input value={boardName} onChange={nameChangeHandler} className="edit-task-title" type="text" name="edit board name" placeholder="e.g. Web Design"/> </div> <div className="edit-board-columns-div"> <label>Board Columns</label> {columns.map((column, index) => ( <div className="edit-columns-item-div" key={index}> <input onChange={e => columnsChangeHandler(index, e)} className="edit-column-input" type="text" name="board" value={column.board} placeholder="e.g. Web Design"/> <svg onClick={() => deleteColumn(index)} key={index} width="15" height="15" xmlns="http://www.w3.org/2000/svg"> <g fill="#828FA3" fillRule="evenodd"><path d="m12.728 0 2.122 2.122L2.122 14.85 0 12.728z"/><path d="M0 2.122 2.122 0 14.85 12.728l-2.122 2.122z"/></g> </svg> </div> ))} </div> <Button onClick={() => dispatch(hideModal())} text="+ Add New Column" className="add-column-subtask"/> <Button onClick={() => { dispatch(hideModal()) dispatch(editBoard(boardName)); }} text="Save Changes" className="create-save-changes"/> </div> ) } export default EditBoardModal
boardSlice.js
import {createSlice} from "@reduxjs/toolkit"; const boardSlice = createSlice({ name: "board", initialState: { boards: [], activeBoard: null }, reducers: { setActiveBoard: (state, {payload}) => { const getBoardByID = state.boards.find(el => el.id === payload); state.activeBoard = getBoardByID; }, addBoard: (state, {payload}) => { const newBoard = { id: payload.id, name: payload.name, columns: payload.columns } if (state.boards.length === 0) { state.activeBoard = newBoard; } state.boards.push(newBoard); }, editBoard: (state, {payload}) => { state.activeBoard.name = payload; }, deleteCurrentBoard: (state, {payload}) => { const deletedBoard = state.boards.filter(item => item.id !== state.activeBoard.id); state.boards = deletedBoard; state.activeBoard = state.boards[0]; }, addColumn: (state, {payload}) => { state.activeBoard.columns.push(payload) } } }); export const { addBoard, setActiveBoard, deleteCurrentBoard, addColumn, editBoard } = boardSlice.actions; export default boardSlice.reducer;
问题原因
- 浅拷贝导致的引用绑定:初始化
columns状态时用[...activeBoard.columns]只是对数组做浅拷贝,数组内的每个column对象仍和Redux store中的对象共享引用。Redux Toolkit通过Immer冻结了store中的对象,外部直接修改这些对象的属性会触发只读限制。 - 硬编码数组无引用关联:硬编码的数组是全新的本地对象,没有和store对象绑定,因此可以正常修改。
解决方案
1. 深拷贝columns数组,断开store引用
初始化本地columns状态时,对每个column对象单独拷贝,彻底断开和store的引用:
const [columns, setColumns] = useState( activeBoard.columns.map(column => ({...column})) );
2. 优化columnsChangeHandler,保证不可变更新
修改列属性时,拷贝目标column对象后再修改,保持不可变更新的规范:
const columnsChangeHandler = (i, e) => { setColumns(prevColumns => { const updatedColumns = [...prevColumns]; updatedColumns[i] = { ...updatedColumns[i], [e.target.name]: e.target.value }; return updatedColumns; }); };
3. 扩展editBoard reducer,支持columns更新
当前editBoard仅处理boardName,需要扩展为接收完整的board数据:
首先修改组件中的dispatch逻辑:
<Button onClick={() => { dispatch(hideModal()) dispatch(editBoard({name: boardName, columns: columns})); }} text="Save Changes" className="create-save-changes"/>
然后更新boardSlice中的reducer:
editBoard: (state, {payload}) => { const boardIndex = state.boards.findIndex(board => board.id === state.activeBoard.id); if (boardIndex !== -1) { // 更新boards数组中的目标board state.boards[boardIndex] = { ...state.boards[boardIndex], name: payload.name, columns: payload.columns }; // 同步更新activeBoard state.activeBoard = {...state.activeBoard, ...payload}; } },
4. 补充删除列函数(可选)
组件中deleteColumn未定义,补充实现:
const deleteColumn = (index) => { setColumns(prevColumns => prevColumns.filter((_, i) => i !== index)); };
内容的提问来源于stack exchange,提问作者envincebal
相关产品推荐
相关产品推荐

