Redux存储中训练列表增删训练组操作缓慢及批量删除失效问题的解决方案咨询
问题分析与解决方案
首先,咱们先拆解你遇到的两个问题:状态更新慢、快速连续删除仅生效一个。本质上,这两个问题的核心都不在reducer本身——你的reducer写法是符合Redux不可变性原则的纯函数,同步执行的它不会造成延迟。问题出在异步操作的流程上:
为什么会出现这些问题?
- 状态更新慢:
DELETE_SET_WORKOUT_SUCCESS是API请求成功后触发的action,延迟来自后端接口的响应时间,而非reducer的执行速度。 - 快速删除仅生效一个:当你连续快速点击删除时,多个异步删除请求会同时发起,每个请求都是基于「发起请求时的旧state」。当第一个请求成功返回更新state后,第二个请求的响应依然用旧state计算新状态,会直接覆盖第一个的更新,最终只有最后一次删除生效。
接下来给你两种针对性的解决方案:
方案一:乐观更新(解决延迟+覆盖问题)
乐观更新的思路是:先在前端更新状态,再发送API请求,用户能立刻看到变化,不会觉得卡顿;同时后续的删除操作会基于最新的state,不会出现覆盖问题。如果API请求失败,再回滚状态即可。
1. 添加同步action与reducer处理
// 新增两个同步action类型 const DELETE_SET_OPTIMISTIC = 'DELETE_SET_OPTIMISTIC'; const ROLLBACK_DELETED_SET = 'ROLLBACK_DELETED_SET'; // 更新reducer case DELETE_SET_OPTIMISTIC: // 和原成功action逻辑一致,先在前端删除 return { ...state, workout: { ...state.workout, exerciseList: state.workout.exerciseList.map(exerciseListItem => { if (exerciseListItem.elid !== action.payload.elid) { return exerciseListItem; } return { ...exerciseListItem, sets: exerciseListItem.sets.filter((set) => set.sid !== action.payload.sid) } }) } }; case ROLLBACK_DELETED_SET: // 请求失败时,把删除的训练组加回去 return { ...state, workout: { ...state.workout, exerciseList: state.workout.exerciseList.map(exerciseListItem => { if (exerciseListItem.elid !== action.payload.elid) { return exerciseListItem; } return { ...exerciseListItem, sets: [...exerciseListItem.sets, action.payload.deletedSet] } }) } };
2. 改造action creator(以redux-thunk为例)
export const deleteSet = (elid, sid, deletedSet) => async (dispatch) => { // 1. 先触发乐观更新,立刻删除前端状态 dispatch({ type: DELETE_SET_OPTIMISTIC, payload: { elid, sid } }); try { // 2. 发送API请求 await yourApi.deleteWorkoutSet(elid, sid); // 请求成功无需额外操作,因为已经更新过状态了 } catch (error) { // 3. 请求失败,回滚状态 dispatch({ type: ROLLBACK_DELETED_SET, payload: { elid, deletedSet } }); // 这里可以添加错误提示,比如toast告知用户删除失败 } };
方案二:串行执行删除操作(解决覆盖问题)
如果不想用乐观更新,你可以让删除操作排队,确保前一个删除的API请求完成后,再执行下一个。同样以redux-thunk为例:
// 维护一个删除队列和执行状态标记 let isDeleting = false; const deleteQueue = []; export const deleteSet = (elid, sid) => async (dispatch) => { // 把当前删除任务加入队列 deleteQueue.push({ elid, sid }); // 如果正在执行删除,直接返回,等待队列处理 if (isDeleting) return; isDeleting = true; // 循环处理队列中的任务 while (deleteQueue.length > 0) { const { elid, sid } = deleteQueue.shift(); try { // 等待API请求完成 await yourApi.deleteWorkoutSet(elid, sid); // 请求成功后再更新状态 dispatch({ type: DELETE_SET_WORKOUT_SUCCESS, payload: { elid, sid } }); } catch (error) { // 处理错误,比如跳过当前任务或重试 console.error('删除失败:', error); } } isDeleting = false; };
额外优化:简化reducer代码
虽然reducer不是核心问题,但你可以用Redux Toolkit的Immer来简化不可变性操作,代码更简洁不易出错:
import { createSlice } from '@reduxjs/toolkit'; const workoutSlice = createSlice({ name: 'workout', initialState: { /* 你的初始状态 */ }, reducers: { deleteSetSuccess: (state, action) => { const { elid, sid } = action.payload; // Immer允许直接修改状态,自动处理不可变性 const targetExercise = state.workout.exerciseList.find(item => item.elid === elid); if (targetExercise) { targetExercise.sets = targetExercise.sets.filter(set => set.sid !== sid); } } } }); export const { deleteSetSuccess } = workoutSlice.actions; export default workoutSlice.reducer;
总结
- 状态更新慢:用乐观更新让用户立刻看到变化,掩盖API延迟的感知;
- 快速删除覆盖:要么用乐观更新基于最新state操作,要么用串行队列确保前一个删除完成后再执行下一个;
- reducer本身可以用Redux Toolkit简化,但不是解决当前问题的核心。
内容的提问来源于stack exchange,提问作者maxi hraschan
相关产品推荐
相关产品推荐

