能否在Redux Reducer中解析Promise?附React项目Fetch请求代码
先直接给你结论:绝对不能在Redux Reducer里解析Promise,这完全违背了Redux的核心设计原则。
为什么不行?
Redux的Reducer必须是纯函数——简单来说,就是给定相同的输入(当前state和action),必须返回完全相同的输出,而且不能有任何副作用(比如异步操作、修改外部变量、调用API等)。
Promise是异步操作的载体,解析它意味着Reducer的执行结果会依赖异步操作的时序,这会让状态变化变得不可预测:你无法确定Reducer什么时候会完成状态更新,也没办法保证相同的输入总能得到相同的输出,这会彻底破坏Redux的可调试性和单向数据流。
正确的做法:用Redux中间件处理异步
Redux专门设计了中间件来处理异步逻辑,比如最常用的Redux Thunk,或者Redux Toolkit里的createAsyncThunk。结合你给出的fetchQuote函数,我给你举个实际的例子:
首先先整理你的fetchQuote函数(稍微优化了一下):
function fetchQuote() { return fetch("http://localhost:8080/quote") .then(response => { if (!response.ok) { throw Error(response.statusText); } return response.text(); }) .catch(error => { console.log('Looks like there was a problem: \n', error); throw error; // 重新抛出错误,让后续处理能捕获 }); }
步骤1:定义Action Types
先声明几个同步的action类型,用来标记请求的不同状态:
const FETCH_QUOTE_REQUEST = 'FETCH_QUOTE_REQUEST'; // 请求开始 const FETCH_QUOTE_SUCCESS = 'FETCH_QUOTE_SUCCESS'; // 请求成功 const FETCH_QUOTE_FAILURE = 'FETCH_QUOTE_FAILURE'; // 请求失败
步骤2:创建异步Action Creator(用Redux Thunk)
Thunk允许你创建返回函数的action creator,这个函数可以调用dispatch,在异步操作的不同阶段分发同步action:
export const fetchQuoteAsync = () => { return dispatch => { // 第一步:告诉Reducer请求开始了,把loading状态设为true dispatch({ type: FETCH_QUOTE_REQUEST }); // 执行你的fetch请求 fetchQuote() .then(quote => { // 请求成功:分发成功action,把返回的quote传进去 dispatch({ type: FETCH_QUOTE_SUCCESS, payload: quote }); }) .catch(error => { // 请求失败:分发失败action,把错误信息传进去 dispatch({ type: FETCH_QUOTE_FAILURE, payload: error.message }); }); }; };
步骤3:在Reducer里处理同步Action
现在Reducer只需要处理这些同步的action,纯同步更新状态:
const initialState = { loading: false, quote: '', error: '' }; const quoteReducer = (state = initialState, action) => { switch (action.type) { case FETCH_QUOTE_REQUEST: return { ...state, loading: true, error: '' // 清空之前的错误 }; case FETCH_QUOTE_SUCCESS: return { ...state, loading: false, quote: action.payload // 更新quote内容 }; case FETCH_QUOTE_FAILURE: return { ...state, loading: false, error: action.payload // 保存错误信息 }; default: return state; } };
(可选)用Redux Toolkit简化代码
如果你的项目用了Redux Toolkit(现在官方推荐的方式),可以用createAsyncThunk和createSlice来大幅简化代码,不用手动写action types和很多模板代码:
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; // 创建异步Thunk export const fetchQuoteAsync = createAsyncThunk( 'quote/fetchQuote', async () => { const response = await fetch("http://localhost:8080/quote"); if (!response.ok) throw Error(response.statusText); return response.text(); } ); // 创建Slice const quoteSlice = createSlice({ name: 'quote', initialState: { loading: false, quote: '', error: '' }, extraReducers: (builder) => { builder .addCase(fetchQuoteAsync.pending, (state) => { state.loading = true; state.error = ''; }) .addCase(fetchQuoteAsync.fulfilled, (state, action) => { state.loading = false; state.quote = action.payload; }) .addCase(fetchQuoteAsync.rejected, (state, action) => { state.loading = false; state.error = action.error.message; }); } }); export default quoteSlice.reducer;
总结
Reducer的唯一职责是纯同步地更新状态,所有异步操作(包括解析Promise)都必须交给Redux中间件来处理,这样才能保证Redux状态的可预测性和整个应用数据流的清晰性。
内容的提问来源于stack exchange,提问作者Nespony

