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

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)

解决方案

问题根源

  1. 直接修改引用类型数据:task = this.state[e.target.name]拿到的是state中对象的引用,直接修改task['complete']会破坏React状态不可变原则;当Redux Store更新后,组件props与state的引用冲突会引发报错。
  2. 组件state与Redux state同步缺失:仅在componentDidMount中同步一次props数据,后续Redux state更新时组件state未同步,导致数据不一致。

修复步骤

  1. 修改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() });
    }
    
  2. 修正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);
    }
    
  3. 添加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
            });
        }
    }
    
  4. 可选:拆分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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 04:15:01