Redux Reducer中多isFetching实例问题及最优解决方案问询
解决Redux多请求共用isFetching导致UI错误加载的最佳实践
这个问题我之前也碰到过,确实挺闹心——共用一个isFetching布尔值状态,只要任意一个请求触发,整个UI就显示加载,完全没法区分不同请求的状态。既不想拆分reducer增加复杂度,又不想重复定义一堆类似isFetchingPublishedPosts、isFetchingSinglePost的冗余字段,对吧?给你几个亲测好用的最佳实践:
方案1:将isFetching改为状态对象(最直观的基础方案)
把原本单个的布尔值isFetching改成一个对象,用请求类型作为key来存储对应请求的加载状态。这样既不用拆分reducer,又能精准控制每个请求的加载UI。
实现步骤:
- 更新Reducer初始状态:
const initialState = { posts: [], // 存储列表数据 singlePost: null, // 存储单条帖子数据 isFetching: { publishedPosts: false, // 对应publishedPosts请求的加载状态 singlePost: false // 对应singlePost请求的加载状态 }, error: null };
- 在Reducer中处理对应Action:
针对不同请求的REQUEST、SUCCESS、FAILURE动作,更新对象中对应的key值:
function postsReducer(state = initialState, action) { switch (action.type) { case 'PUBLISHED_POSTS_REQUEST': return { ...state, isFetching: { ...state.isFetching, publishedPosts: true } }; case 'PUBLISHED_POSTS_SUCCESS': return { ...state, posts: action.payload, isFetching: { ...state.isFetching, publishedPosts: false } }; case 'PUBLISHED_POSTS_FAILURE': return { ...state, error: action.payload, isFetching: { ...state.isFetching, publishedPosts: false } }; // 处理singlePost的动作 case 'SINGLE_POST_REQUEST': return { ...state, isFetching: { ...state.isFetching, singlePost: true } }; case 'SINGLE_POST_SUCCESS': return { ...state, singlePost: action.payload, isFetching: { ...state.isFetching, singlePost: false } }; case 'SINGLE_POST_FAILURE': return { ...state, error: action.payload, isFetching: { ...state.isFetching, singlePost: false } }; default: return state; } }
- 组件中映射对应状态:
在mapStateToProps中精准获取对应请求的加载状态,避免混淆:
const mapStateToProps = (state) => ({ publishedPosts: state.posts.posts, singlePost: state.posts.singlePost, isLoadingPosts: state.posts.isFetching.publishedPosts, isLoadingSinglePost: state.posts.isFetching.singlePost });
方案2:利用Action的meta元数据简化Reducer逻辑
如果不想写太多重复的case判断,可以给每个Action添加meta字段标记请求类型,然后在Reducer中统一处理,减少冗余代码。
实现步骤:
- 带meta字段的Action创建函数:
export const fetchPublishedPosts = () => ({ type: 'FETCH_POSTS_REQUEST', meta: { requestType: 'publishedPosts' } }); export const fetchSinglePost = (postId) => ({ type: 'FETCH_SINGLE_POST_REQUEST', payload: postId, meta: { requestType: 'singlePost' } }); // 成功/失败动作同理,也要带上meta export const fetchPublishedPostsSuccess = (data) => ({ type: 'FETCH_POSTS_SUCCESS', payload: data, meta: { requestType: 'publishedPosts' } });
- Reducer中统一处理状态更新:
function postsReducer(state = initialState, action) { const { requestType } = action.meta || {}; switch (action.type) { // 处理所有请求开始的动作 case 'FETCH_POSTS_REQUEST': case 'FETCH_SINGLE_POST_REQUEST': return { ...state, isFetching: { ...state.isFetching, [requestType]: true } }; // 处理所有请求成功/失败的动作 case 'FETCH_POSTS_SUCCESS': case 'FETCH_POSTS_FAILURE': case 'FETCH_SINGLE_POST_SUCCESS': case 'FETCH_SINGLE_POST_FAILURE': return { ...state, isFetching: { ...state.isFetching, [requestType]: false }, // 按需更新数据或错误信息 ...(action.payload && { [requestType === 'publishedPosts' ? 'posts' : 'singlePost']: action.payload }), ...(action.error && { error: action.error }) }; default: return state; } }
方案3:使用Redux Toolkit(官方推荐的现代化方案)
如果你的项目已经在用Redux Toolkit(RTK),这会是最简洁高效的方案。RTK自带的createAsyncThunk和createSlice可以自动生成异步请求的pending/fulfilled/rejected动作,还通过Immer简化了状态更新逻辑。
实现步骤:
- 创建异步Thunk:
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import api from './api'; // 假设你的API请求工具 // 获取发布的帖子列表 export const fetchPublishedPosts = createAsyncThunk( 'posts/fetchPublishedPosts', async () => { const response = await api.get('/posts/published'); return response.data; } ); // 获取单条帖子 export const fetchSinglePost = createAsyncThunk( 'posts/fetchSinglePost', async (postId) => { const response = await api.get(`/posts/${postId}`); return response.data; } );
- 创建Posts Slice:
const postsSlice = createSlice({ name: 'posts', initialState: { list: [], single: null, isFetching: { publishedPosts: false, singlePost: false }, error: null }, extraReducers: (builder) => { builder // 处理publishedPosts的异步状态 .addCase(fetchPublishedPosts.pending, (state) => { state.isFetching.publishedPosts = true; }) .addCase(fetchPublishedPosts.fulfilled, (state, action) => { state.isFetching.publishedPosts = false; state.list = action.payload; state.error = null; }) .addCase(fetchPublishedPosts.rejected, (state) => { state.isFetching.publishedPosts = false; state.error = '获取帖子列表失败'; }) // 处理singlePost的异步状态 .addCase(fetchSinglePost.pending, (state) => { state.isFetching.singlePost = true; }) .addCase(fetchSinglePost.fulfilled, (state, action) => { state.isFetching.singlePost = false; state.single = action.payload; state.error = null; }) .addCase(fetchSinglePost.rejected, (state) => { state.isFetching.singlePost = false; state.error = '获取单条帖子失败'; }); } }); export default postsSlice.reducer;
- 组件中使用状态:
和方案1一样,直接映射对应请求的加载状态即可,RTK已经帮你处理了状态的不可变性问题,代码更简洁。
内容的提问来源于stack exchange,提问作者Matt Saunders
相关产品推荐
相关产品推荐

