React Redux中修改嵌套数组对象内座位status值的方法
React-Redux 电影选座功能座位状态更新实现
现有代码结构
开发电影选座功能时,初始定义的座位数据源结构如下:
data = [{ class: "A", seat: [ { id: "A1", price: 7.5, status: false }, { id: "A2", price: 7.5, status: false }, { id: "A3", price: 7.5, status: false }, { id: "A4", price: 7.5, status: false }, ], }, { class: "B", seat: [ { id: "B1", price: 7.5, status: false }, { id: "B2", price: 7.5, status: false }, { id: "B3", price: 7.5, status: false }, { id: "B4", price: 7.5, status: false }, ], } ]
项目中初始编写的movieReducer代码如下:
const stateMovie = { cart: [], data: data, }; export const movieReducer = (state = stateMovie, action) => { case "ADD_TO_CART": { let cloneCart = [...state.cart]; let index1 = cloneCart.findIndex((sp) => { return sp.id == action.seatItem.id; }); let cloneData = [...state.data]; if (index1 == -1) { cloneCart.push(action.seatItem); action.seatItem.status = "selecting"; } state.data = cloneData; state.cart = cloneCart; return { ...state }; } case "REMOVE_FROM_CART": { let cloneCart = [...state.cart]; cloneCart.splice(action.index, 1); let cloneData = [...state.data]; state.data = cloneData; state.cart = cloneCart; return { ...state }; } case "CONFIRM_BOOKING": { let cloneData = [...state.data] state.data = cloneData; return { ...state }; }
触发ADD_TO_CART动作选中座位后,state.data中对应座位的status字段会更新为'selecting',此时数据结构示例:
data = [{ class: "A", seat: [ { id: "A1", price: 7.5, status: false }, { id: "A2", price: 7.5, status: false }, { id: "A3", price: 7.5, status: false }, { id: "A4", price: 7.5, status: false }, ], }, { class: "B", seat: [ { id: "B1", price: 7.5, status: false }, { id: "B2", price: 7.5, status: 'selecting' }, { id: "B3", price: 7.5, status: false }, { id: "B4", price: 7.5, status: false }, ], } ]
需求说明
在CONFIRM_BOOKING动作的处理逻辑中,需要将已加入购物车的座位在state.data内对应的status值从'selecting'修改为true,实现必须符合Redux不可变数据更新规范。
符合规范的实现方案
Redux不可变更新核心规则:禁止直接修改原state上的任何属性,所有发生值变化的嵌套层级都要创建新的对象/数组副本,未修改的节点保留原引用,兼顾不可变要求和渲染性能。
首先修正原有reducer中浅拷贝不彻底、直接修改原对象引用的问题,再补全CONFIRM_BOOKING逻辑,完整代码如下:
const stateMovie = { cart: [], data: data, }; export const movieReducer = (state = stateMovie, action) => { switch (action.type) { case "ADD_TO_CART": { const existSeat = state.cart.find(sp => sp.id === action.seatItem.id); if (existSeat) return state; // 分层创建副本更新座位状态 const newData = state.data.map(row => { if (!row.seat.some(seat => seat.id === action.seatItem.id)) return row; return { ...row, seat: row.seat.map(seat => { if (seat.id === action.seatItem.id) { return { ...seat, status: "selecting" }; } return seat; }) } }); return { ...state, cart: [...state.cart, { ...action.seatItem, status: "selecting" }], data: newData }; } case "REMOVE_FROM_CART": { const removedSeat = state.cart[action.index]; const newData = state.data.map(row => { if (!row.seat.some(seat => seat.id === removedSeat.id)) return row; return { ...row, seat: row.seat.map(seat => { if (seat.id === removedSeat.id) { return { ...seat, status: false }; } return seat; }) } }); return { ...state, cart: state.cart.filter((_, idx) => idx !== action.index), data: newData }; } case "CONFIRM_BOOKING": { // 用Set存储选中座位ID,把匹配时间复杂度从O(n)降到O(1) const selectedSeatIds = new Set(state.cart.map(seat => seat.id)); const newData = state.data.map(row => { // 提前判断当前行是否有需要更新的座位,无更新直接返回原行减少性能消耗 const hasUpdateSeat = row.seat.some(seat => selectedSeatIds.has(seat.id) && seat.status === "selecting"); if (!hasUpdateSeat) return row; // 需要更新的行创建新对象副本,座位数组同步生成新副本 return { ...row, seat: row.seat.map(seat => { if (selectedSeatIds.has(seat.id) && seat.status === "selecting") { return { ...seat, status: true }; } return seat; }) } }); // 确认购票后清空购物车,返回全新state对象 return { ...state, cart: [], data: newData }; } default: return state; } }
实现要点
- 全程没有直接修改原state或原嵌套对象的属性,所有更新都通过展开运算符生成新副本
- 遍历过程中跳过无修改的节点,保留原引用,避免不必要的组件重渲染
- 修正了原有逻辑中直接修改
action.seatItem、浅拷贝数组后仍操作嵌套引用的问题,避免状态污染 - 确认购票后自动清空购物车,符合常规选座购票流程逻辑
内容的提问来源于stack exchange,提问作者Jackson
相关产品推荐
相关产品推荐

