Redux嵌套对象更新更优语法:简化reducer操作的方案咨询
简化深层嵌套Redux Reducer的更新逻辑
问题场景
我当前有一个处理深层嵌套状态的reducer函数,写法冗长且可读性极差:
_({ expandAbility: (state, a: { which: string }) => ({ ...state, report: state.report && { waitingForIt: false, content: state.report.content && { ...state.report.content, interaction: { expandAbilities: !state.report.content.interaction.expandAbilities.contains(a.which) ? state.report.content.interaction.expandAbilities.add(a.which) : state.report.content.interaction.expandAbilities.remove(a.which) } } } }), })
对应的State类型参考:
const initialState = { report: undefined as | { waitingForIt?: boolean content?: { supportedFightIds: number[] deathCut: number reportInfo: any deathsFull: any deathsByPlayer: any deathsByAbility: any interaction: { expandAbilities: Set<string> } } } | undefined, error: undefined as Error | undefined }
除了创建变量引用深层路径之外,有没有什么技巧或流行库能让这类更新操作的写法更简洁、易读?
回答
嘿,这种嵌套地狱式的reducer写法确实让人头疼!除了用变量拆解路径,有不少实用的方案能帮你简化代码,我来分享几个当前主流的选项:
1. Immer + Redux Toolkit(最推荐)
Immer是现在处理不可变状态的行业标准,而Redux Toolkit(RTK)已经默认集成了它,完全不用额外配置。它允许你用"直接修改"的语法写逻辑,背后会自动帮你生成不可变的新状态,彻底告别层层展开的痛苦。
用RTK改写你的expandAbility reducer会简洁太多:
import { createSlice } from '@reduxjs/toolkit' const reportSlice = createSlice({ name: 'report', initialState, reducers: { expandAbility: (state, action: { payload: { which: string } }) => { // 先做空值判断,避免报错 if (!state.report || !state.report.content) return; // 直接修改状态,Immer会处理不可变性 state.report.waitingForIt = false; const { which } = action.payload; const abilitiesSet = state.report.content.interaction.expandAbilities; if (abilitiesSet.has(which)) { abilitiesSet.delete(which); } else { abilitiesSet.add(which); } } } }) // 导出action和reducer export const { expandAbility } = reportSlice.actions; export default reportSlice.reducer;
如果单独用Immer(不使用RTK),写法也类似:
import { produce } from 'immer' // 在原reducer中使用produce expandAbility: produce((state, a: { which: string }) => { if (!state.report || !state.report.content) return; state.report.waitingForIt = false; const abilities = state.report.content.interaction.expandAbilities; abilities.has(a.which) ? abilities.delete(a.which) : abilities.add(a.which); })
2. Ramda的透镜(Lenses)
如果你偏好函数式编程风格,Ramda的透镜工具可以让你精准聚焦到深层嵌套的状态路径,然后进行更新操作,避免手动处理层层展开。
示例代码:
import { lensPath, over, set } from 'ramda'; // 定义指向expandAbilities的透镜 const expandAbilitiesLens = lensPath(['report', 'content', 'interaction', 'expandAbilities']); // 定义指向report.waitingForIt的透镜 const reportWaitingLens = lensPath(['report', 'waitingForIt']); // 编写切换能力的纯函数 const toggleAbility = (which: string) => (abilities: Set<string>) => { const newSet = new Set(abilities); abilities.has(which) ? newSet.delete(which) : newSet.add(which); return newSet; }; // 在reducer中组合使用 expandAbility: (state, a: { which: string }) => { // 先处理waitingForIt的更新 const withWaitingUpdated = state.report ? set(reportWaitingLens, false, state) : state; // 再处理能力集合的切换 return over(expandAbilitiesLens, toggleAbility(a.which), withWaitingUpdated); }
3. 纯JS/TS解构优化(无额外依赖)
如果不想引入任何新库,通过解构和局部变量的方式,也能大幅提升原代码的可读性,比直接嵌套展开清爽很多:
expandAbility: (state, a: { which: string }) => { // 空值判断,直接返回原状态 if (!state.report || !state.report.content) return state; // 解构出各层局部变量,避免重复写长路径 const { report } = state; const { content } = report; const { interaction } = content; const { expandAbilities: oldAbilities } = interaction; // 创建新的Set,保证不可变性 const newAbilities = new Set(oldAbilities); if (newAbilities.has(a.which)) { newAbilities.delete(a.which); } else { newAbilities.add(a.which); } // 逐层展开更新 return { ...state, report: { ...report, waitingForIt: false, content: { ...content, interaction: { ...interaction, expandAbilities: newAbilities } } } }; }
总结
如果是基于Redux开发,Redux Toolkit + Immer绝对是当前的最佳实践——它不仅简化了代码,还内置了很多Redux的最佳实践,社区支持也最完善。Ramda适合喜欢函数式编程的场景,而纯解构优化则适合不想增加依赖的小型项目。
内容的提问来源于stack exchange,提问作者wkrueger
相关产品推荐
相关产品推荐

