如何在createAsyncThunk的setTimeout中分发action实现延迟计数?
用Redux Toolkit的createAsyncThunk实现延迟加减计数器
我正在学习使用createAsyncThunk,已经做了一个简易计数器应用,有普通的加、减按钮,点击后数值实时增减。现在想新增两个按钮,点击后同样实现加减,但要延迟1秒完成。我已经用非Redux Toolkit的方式实现了这个功能,但不知道怎么用Redux Toolkit来做。我尝试写了部分代码,但不清楚createAsyncThunk的回调该怎么写,之前尝试在回调里分发action没成功,我的代码如下:
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; export const asyncAdd = createAsyncThunk('asyncAdd', async (_, thunkAPI) => { }); const counterSlice = createSlice({ name: 'counter', initialState: { count: 0, hasError: false, isLoading: false, users: {}, todos: {}, }, reducers: { add: (state, action) => { state.count += action.payload; }, sub: (state) => { state.count -= 1; }, }, extraReducers: { [asyncAdd.pending]: (state, action) => { state.hasError = false; state.isLoading = true; console.log('PENDING'); }, [asyncAdd.fulfilled]: (state, action) => { state.hasError = false; state.isLoading = false; console.log('payload ' + action.payload); state.count = action.payload; console.log('FULILLED'); }, [asyncAdd.rejected]: (state, action) => { state.hasError = true; state.isLoading = false; console.log('REJECTED'); }, }, }); export const { add, sub } = counterSlice.actions; export default counterSlice.reducer;
实现方案
1. 完善异步Thunk函数
createAsyncThunk不需要手动分发action,只需在回调里完成延迟逻辑,返回最终要更新的数值即可。我们需要分别实现延迟加、延迟减两个Thunk:
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; // 延迟加1的异步Thunk export const asyncAdd = createAsyncThunk('counter/asyncAdd', async (_, thunkAPI) => { // 延迟1秒 await new Promise(resolve => setTimeout(resolve, 1000)); // 获取当前计数器数值 const currentCount = thunkAPI.getState().counter.count; // 返回新的数值 return currentCount + 1; }); // 延迟减1的异步Thunk export const asyncSub = createAsyncThunk('counter/asyncSub', async (_, thunkAPI) => { await new Promise(resolve => setTimeout(resolve, 1000)); const currentCount = thunkAPI.getState().counter.count; return currentCount - 1; });
2. 在extraReducers中处理异步状态
推荐使用Redux Toolkit官方推荐的builder链式写法,类型更安全且可读性更高:
const counterSlice = createSlice({ name: 'counter', initialState: { count: 0, hasError: false, isLoading: false, }, reducers: { add: (state, action) => { state.count += action.payload; }, sub: (state) => { state.count -= 1; }, }, extraReducers: (builder) => { // 处理asyncAdd的三种状态 builder .addCase(asyncAdd.pending, (state) => { state.isLoading = true; state.hasError = false; }) .addCase(asyncAdd.fulfilled, (state, action) => { state.isLoading = false; state.count = action.payload; }) .addCase(asyncAdd.rejected, (state) => { state.isLoading = false; state.hasError = true; }) // 处理asyncSub的三种状态 .addCase(asyncSub.pending, (state) => { state.isLoading = true; state.hasError = false; }) .addCase(asyncSub.fulfilled, (state, action) => { state.isLoading = false; state.count = action.payload; }) .addCase(asyncSub.rejected, (state) => { state.isLoading = false; state.hasError = true; }); }, }); export const { add, sub } = counterSlice.actions; export default counterSlice.reducer;
3. 组件中调用异步Thunk
在组件里通过useDispatch分发异步Thunk即可,同时可以用isLoading状态防止重复点击:
import { useDispatch, useSelector } from 'react-redux'; import { add, sub, asyncAdd, asyncSub } from './counterSlice'; function Counter() { const { count, isLoading } = useSelector(state => state.counter); const dispatch = useDispatch(); return ( <div> <h1>当前数值:{count}</h1> <button onClick={() => dispatch(add(1))}>普通加1</button> <button onClick={() => dispatch(sub())}>普通减1</button> <button onClick={() => dispatch(asyncAdd())} disabled={isLoading}>延迟加1(1秒)</button> <button onClick={() => dispatch(asyncSub())} disabled={isLoading}>延迟减1(1秒)</button> {isLoading && <p>处理中...</p>} </div> ); }
核心注意事项
- 不要在
createAsyncThunk的回调里手动分发action,Thunk会自动根据异步状态生成对应action,我们只需返回最终数据。 - 如需获取当前状态,使用
thunkAPI.getState()方法即可。 - 利用
isLoading状态禁用按钮,避免用户重复触发异步操作。
内容的提问来源于stack exchange,提问作者Gautham Muralidharan
相关产品推荐
相关产品推荐

