如何在React函数组件中用useReducer更新嵌套对象状态且不丢失值?
问题分析与修复方案
核心问题
- reducer参数错误:原reducer仅接收单个参数,正确的
useReducerreducer签名应为(state, action),第一个参数是当前状态,第二个是dispatch传递的动作标识。 - 状态结构被破坏:
Strength_Inc分支中,使用Object.keys(state['SPECIAL']).map(...)返回数组,完全改变了state的原始结构(原state是包含SPECIAL、Skills、Stats的对象),导致后续渲染时state.SPECIAL不存在,所有内容丢失。- 其他Inc分支仅返回SPECIAL对象的副本,丢失了Skills和Stats属性,点击后state不再包含这两个字段,对应渲染内容消失。
- 缺少减号动作处理:页面中的减号按钮会dispatch
key + '_Dec'动作,但reducer中未实现对应逻辑。
修复后的完整代码
import { useReducer } from 'react'; function App(){ const [state, dispatch] = useReducer(reducer, { SPECIAL: { Strength: 5, Perception: 5, Endurance: 5, Charisma: 5, Intelligence: 5, Agility: 5, Luck: 5}, Skills: { Barter: 3, Energy_Weapons: 3, Explosives: 3, Guns: 3, Lockpick: 3, Medicine: 3, Melee_Weapons: 3, Repair: 3, Science: 3, Sneak: 3, Speech: 3, Survival: 3, Unarmed: 3}, Stats: { Action_Points: 65, Carry_Weight: 150, Critical_Chance: 0, Damage_Resistance: 0, Damage_Threshold: 0, Hit_Points: 100, Melee_Damage: 0, Rad_Resistance: 2, Skill_Rate: 0, Unarmed_Damage: 0} }) function reducer(state, action){ const [statName, operation] = action.split('_'); switch (action){ // 处理所有加值动作 case 'Strength_Inc': case 'Perception_Inc': case 'Endurance_Inc': case 'Charisma_Inc': case 'Intelligence_Inc': case 'Agility_Inc': case 'Luck_Inc': return { ...state, // 保留原状态的Skills和Stats SPECIAL: { ...state.SPECIAL, // 复制原SPECIAL对象 [statName]: state.SPECIAL[statName] + 1 // 修改目标属性值 } } // 处理所有减值动作,增加下限判断 case 'Strength_Dec': case 'Perception_Dec': case 'Endurance_Dec': case 'Charisma_Dec': case 'Intelligence_Dec': case 'Agility_Dec': case 'Luck_Dec': if (state.SPECIAL[statName] <= 0) return state; return { ...state, SPECIAL: { ...state.SPECIAL, [statName]: state.SPECIAL[statName] - 1 } } default: return state } } return ( <div style={{ color: "green", display: "block" }}> {Object.entries(state.SPECIAL).map(([key, value]) => ( <p className='SPECIAL'> <button onClick={() => dispatch(key + '_Inc')}>+</button> {value} <button onClick={() => dispatch(key + '_Dec')}>-</button> {key} </p> ))} {Object.entries(state.Skills).map(([key, value]) => ( <p className='Skills'> {value} {key} </p> ))} {Object.entries(state.Stats).map(([key, value]) => ( <p className='Stats'> {value} {key} </p> ))} </div> ) }; export default App;
修复要点
- 每次返回完整的state对象,通过扩展运算符
...state保留Skills和Stats,再重新生成SPECIAL对象修改目标属性,确保状态结构一致。 - 拆分action字符串统一处理Inc/Dec动作,减少重复代码。
- 增加数值下限判断,避免属性值变为负数(可根据需求调整)。
内容的提问来源于stack exchange,提问作者bascott
相关产品推荐
相关产品推荐

