在Redux Toolkit中如何在一个reducer内部调用另一个reducer函数
Redux Toolkit中reducer间调用的正确方式
核心问题拆解
你遇到的两个错误本质上都是违反了Redux的核心规则:
- 第一个错误:Redux reducer是纯函数,执行时store处于锁定状态,绝对不能在reducer内部调用
dispatch,否则会触发新的state更新,导致状态冲突。 - 第二个错误:Redux Toolkit用Immer包装的
state是临时Proxy对象,reducer执行完成后会被立即撤销,异步回调(比如setInterval)里访问这个过期的Proxy就会触发撤销错误。
正确解决方案
一、把异步/副作用逻辑移出reducer
定时器、DOM音频播放这类属于副作用操作,应该放到Redux Thunk或者组件中处理,reducer只负责同步更新状态。
用Thunk封装定时器逻辑
这是最规范的做法,把整个启停定时器的逻辑放到thunk里,在thunk中安全调用dispatch和getState:
// timerSlice.ts import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; import type { ITimer } from './types'; // 定义启停定时器的thunk export const toggleTimer = createAsyncThunk( 'timer/toggleTimer', async (_, { dispatch, getState }) => { const { timer } = getState() as { timer: ITimer }; // 暂停逻辑 if (timer.intervalId) { clearInterval(timer.intervalId); dispatch(updateTimerState({ active: false, intervalId: null })); return; } // 启动逻辑 const intervalId = setInterval(() => { const currentState = getState().timer; if (currentState.timer === 0) { // 播放音频(副作用) const audio = document.getElementById("beep") as HTMLAudioElement; audio.play(); // 触发状态更新 dispatch(toggleStatus()); dispatch(recalculateTimer()); return; } dispatch(decreaseTimer()); }, 1000); dispatch(updateTimerState({ active: true, intervalId })); } ); const timerSlice = createSlice({ name: "timer", initialState, reducers: { updateTimerState: (state, action: PayloadAction<{ active: boolean; intervalId: number | null }>) => { state.active = action.payload.active; state.intervalId = action.payload.intervalId; }, decreaseTimer: (state) => { state.timer -= 1; }, toggleStatus: (state) => { state.status = state.status === "SESSION" ? "BREAK" : "SESSION"; }, recalculateTimer: (state) => { state.timer = state[state.status.toLowerCase() as "session" | "break"] * 60; }, decreaseBreak: (state) => { if (state.active || state.break <= 1) return; state.break -= 1; // 直接复用recalculateTimer的更新逻辑 if (state.status === "BREAK") { timerSlice.caseReducers.recalculateTimer(state, {} as PayloadAction<void>); } } } }); export const { updateTimerState, decreaseTimer, toggleStatus, recalculateTimer, decreaseBreak } = timerSlice.actions; export default timerSlice.reducer;
组件中调用Thunk
// index.tsx import { useDispatch, useSelector } from 'react-redux'; import { toggleTimer } from './timerSlice'; function Timer() { const { active } = useSelector(state => state.timer); const dispatch = useDispatch(); return ( <button id="start_stop" onClick={() => dispatch(toggleTimer())}> {active ? "Pause" : "Start"} </button> ); }
二、在reducer内部复用其他reducer逻辑
如果只是想在一个reducer里复用另一个reducer的状态更新逻辑,不需要调用dispatch,直接通过slice.caseReducers调用对应的reducer函数即可:
// 在decreaseBreak中复用recalculateTimer的逻辑 decreaseBreak: (state) => { if (state.active || state.break <= 1) return; state.break -= 1; if (state.status === "BREAK") { // 直接调用recalculateTimer的reducer函数,传入当前state timerSlice.caseReducers.recalculateTimer(state, {} as PayloadAction<void>); } }
Immer会自动处理state的不可变更新,不需要手动返回新对象。
关键规则回顾
- reducer必须是纯函数:不能包含异步操作、DOM操作、调用
dispatch等副作用。 - 异步逻辑用Thunk/Middleware处理:Thunk允许你在异步回调中安全访问
getState和dispatch。 - 复用reducer逻辑直接调用函数:通过
slice.caseReducers获取reducer函数,直接传入state执行更新。
内容的提问来源于stack exchange,提问作者Jahongir Hayitov
相关产品推荐
相关产品推荐

