如何用React和Redux修改数组中的元素?
在Redux中修改数组指定元素的正确姿势
当然可行!在Redux里更新数组中的某个元素是非常常见的需求,但一定要严格遵守Redux的不可变更新原则——你绝对不能直接修改原state,必须返回一个全新的状态对象,这也是你想使用Object.assign()的原因,这个思路完全正确。
先说说你现有代码里的问题
- Reducer的
EDIT_COMPONENT逻辑错误:你现在的写法是直接试图修改state.co,但你的状态结构里co是components数组中每个元素的属性,而且还写错了Object.assign(写成了Object.assing),完全没处理数组的更新。 - 容器组件的事件参数缺失:你的编辑按钮点击时没有把当前组件的内容和id传递给
onEditComponent,导致reducer拿不到要修改的目标元素和新值。 - 小细节问题:容器组件里的
this.props.compons虽然是自定义别名,但编辑按钮没关联输入框的值,没法获取用户要修改的内容。
修正后的Reducer代码
在EDIT_COMPONENT的case里,我们需要遍历数组,找到对应id的元素,返回一个新数组——新数组里除了目标元素被替换成更新后的版本,其他元素都保持原样。这里用map方法实现不可变更新最直观:
import * as actionTypes from '../actions'; const iniState = { components: [] }; const rootReducer = (state = iniState, action) => { switch (action.type) { case actionTypes.ADD_COMPONENT: const newComponent = { id: Math.random(), co: action.data.compToReducer }; return { ...state, components: state.components.concat(newComponent) }; case actionTypes.DELETE_COMPONENT: return { ...state, components: state.components.filter(component => component.id !== action.index) }; case actionTypes.EDIT_COMPONENT: // 生成新数组,只更新目标元素 const updatedComponents = state.components.map(component => { if (component.id === action.data.index) { // 返回新的元素对象,不修改原数据 return { ...component, co: action.data.componentToReducer }; } // 其他元素直接返回原对象 return component; }); // 返回全新的state对象 return { ...state, components: updatedComponents }; default: return state; } }; export default rootReducer;
修正后的容器组件代码
主要是给编辑按钮绑定正确的事件逻辑,获取输入框的值并传递给Redux的action:
import React, { Component } from 'react'; import { connect } from 'react-redux'; import FoodComponent from '../../components/FoodComponent/FoodComponent'; import AddComponent from '../../components/Buttons/AddComponent'; import * as actionTypes from '../../store/actions'; import classes from './FoodComponents.scss'; class FoodComponents extends Component { // 处理编辑逻辑:获取输入值并触发action handleEdit = (componentId) => { // 获取当前输入框的内容 const inputEl = event.target.previousElementSibling; const newValue = inputEl.value.trim(); if (newValue) { this.props.onEditComponent(newValue, componentId); inputEl.value = ''; // 清空输入框 } }; render() { return ( <div> <AddComponent text="Add component" click={this.props.onAddComponent} /> <ul> { this.props.compons.map(component => ( <li key={component.id} > <p className={classes.Component}>{component.co}</p> <input type="text" placeholder="输入新内容" /> {/* 点击时传递当前组件的id */} <button onClick={() => this.handleEdit(component.id)}> Edit Component </button> <button onClick={() => this.props.onDeleteComponent(component.id)}> Delete component </button> </li> )) } </ul> </div> ) } } const mapStateToProps = state => { return { compons: state.components } }; const mapDispatchToProps = dispatch => { return { onAddComponent: (component) => dispatch({ type: actionTypes.ADD_COMPONENT, data: {compToReducer: component} }), onDeleteComponent: (id) => dispatch({ type: actionTypes.DELETE_COMPONENT, index: id }), onEditComponent: (component, id) => dispatch({ type: actionTypes.EDIT_COMPONENT, data:{componentToReducer: component, index: id} }) } }; export default connect(mapStateToProps, mapDispatchToProps)(FoodComponents);
补充说明
- 为什么必须返回新状态?Redux的reducer是纯函数,依赖状态的引用变化来触发组件更新。如果直接修改原数组或对象,Redux会认为状态没有变化,相关组件不会重新渲染。
- 除了
map方法,你也可以用Object.assign或者展开运算符创建新元素,核心都是不碰原数据,只生成新副本。 - 上面用了获取DOM元素的方式拿输入值,你也可以把输入框改成受控组件,用组件自身的state管理输入内容,这会更符合React的最佳实践。
内容的提问来源于stack exchange,提问作者Michał
相关产品推荐
相关产品推荐

