React Redux Slice Reducer导致组件状态只读问题求助
问题描述
我开发的应用包含多个组件,其中有一个简易待办事项列表。当用户更新列表值或标记任务为完成状态时,希望同步更新Redux Store。在组件的toggleComplete函数中,不调用reducer时能正常切换任务的complete状态;但在saveToDos函数中调用reducer后,执行task['complete'] = !task['complete']会导致应用报错。
configSlice.js
import { createSlice } from '@reduxjs/toolkit'; export const configSlice = createSlice({ name:'config', initialState: { weatherLocation:'', todos:{ mainGoal: { desc:'', complete: false }, priorities_1: { desc:'', complete: false }, priorities_2: { desc:'', complete: false }, priorities_3: { desc:'', complete: false }, other_todos: [] }, links:[] }, reducers: { updateToDos: (state, action) => { state.todos = action.payload }, updateLinks: (state, action) => { state.links = action.payload }, updateLocation: (state, action) => { state.weatherLocation = action.payload } } }) export const { updateToDos, updateLinks, updateLocation} = configSlice.actions export default configSlice.reducer
ToDo.jsx
import React, {Component} from "react"; import {connect} from "react-redux"; import {Auth} from 'aws-amplify'; import {setUser, setAuthorizationToken} from '../auth/authSlice'; import { updateToDos } from "../config/configSlice"; // import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import './todo.css' class Todo extends Component { constructor(props) { super(props) this.state = { edit:false, mainGoal: { desc:'', complete: false }, priorities_1: { desc:'', complete: false }, priorities_2: { desc:'', complete: false }, priorities_3: { desc:'', complete: false }, other_todos: [] } this.handleInput = this.handleInput.bind(this) this.saveToDos = this.saveToDos.bind(this) this.toggleComplete = this.toggleComplete.bind(this) } componentDidMount(){ <<Snip>> let todos = this.props.todos this.setState({todos}) } saveToDos = () => { console.log('saving') let {edit, ...todos} = this.state this.props.updateToDos(todos) } <<Snip>> toggleComplete = e => { console.log(e.target) let task = this.state[e.target.name] console.log(task) task['complete'] = !task['complete'] console.log(task) this.setState({ [e.target.name]: task }, () => {this.saveToDos()}) } render() { return( <div className="todo-panel"> <<SNIP>> <div className="mt-3"> <h6>Today's Main Goal</h6> <div className="input-group"> <input type="text" className={`form-control offset-1 ${this.state.mainGoal.complete && 'strikethrough'}`} value={this.state.mainGoal.desc} readOnly={!this.state.edit} name='mainGoal' onChange={this.handleInput} onBlur = {this.saveToDos} /> <button type="button" className="btn btn-outline-success" name='mainGoal' onClick={this.toggleComplete} >Complete</button> </div> </div> <<SNIP OTHER JSX>> </div> ) } }; function mapStateToProps(state) { return { currentUser: state.auth, todos: state.config.todos }; } export default connect( mapStateToProps, { setUser, updateToDos } )(Todo)
解决方案
问题根源
- 直接修改引用类型数据:
task = this.state[e.target.name]拿到的是state中对象的引用,直接修改task['complete']会破坏React状态不可变原则;当Redux Store更新后,组件props与state的引用冲突会引发报错。 - 组件state与Redux state同步缺失:仅在
componentDidMount中同步一次props数据,后续Redux state更新时组件state未同步,导致数据不一致。
修复步骤
修改
toggleComplete,避免直接修改原对象
创建任务对象的副本,修改副本后再更新state:toggleComplete = e => { const taskName = e.target.name; // 解构创建副本,保证状态不可变 const updatedTask = { ...this.state[taskName], complete: !this.state[taskName].complete }; this.setState({ [taskName]: updatedTask }, () => { this.saveToDos() }); }修正
saveToDos的todos结构
当前代码会把单个任务属性直接作为todos对象,需要重组为Redux要求的结构:saveToDos = () => { console.log('saving'); const { edit, mainGoal, priorities_1, priorities_2, priorities_3, other_todos } = this.state; const todos = { mainGoal, priorities_1, priorities_2, priorities_3, other_todos }; this.props.updateToDos(todos); }添加
componentDidUpdate同步Redux state
当Redux中的todos更新时,同步更新组件state:componentDidUpdate(prevProps) { if (prevProps.todos !== this.props.todos) { const { mainGoal, priorities_1, priorities_2, priorities_3, other_todos } = this.props.todos; this.setState({ mainGoal, priorities_1, priorities_2, priorities_3, other_todos }); } }可选:拆分Redux reducer操作(更优方案)
可以添加细粒度的reducer直接切换单个任务状态,减少全量更新:// 在configSlice.js的reducers中添加 toggleTodoComplete: (state, action) => { const { taskName } = action.payload; state.todos[taskName].complete = !state.todos[taskName].complete; }组件中直接调用该action,无需先更新本地state:
// 引入新action import { updateToDos, toggleTodoComplete } from "../config/configSlice"; // 修改toggleComplete toggleComplete = e => { const taskName = e.target.name; this.props.toggleTodoComplete({ taskName }); // 同步本地state(如果需要保留本地状态) const updatedTask = { ...this.state[taskName], complete: !this.state[taskName].complete }; this.setState({ [taskName]: updatedTask }); }
内容的提问来源于stack exchange,提问作者KevinD
相关产品推荐
相关产品推荐

