React中useReducer结合Axios获取数据异常的问题求助
React useReducer 异步请求问题解决方案
问题重现
你尝试在useReducer的reducer中直接调用异步API请求,虽然回调里能打印数据,但最终存入state的是空对象,渲染时无法正常显示内容。你的代码如下:
import React, { useReducer } from "react"; import axios from "axios"; export const ACTIONS = { ADD_STANDARD: 'add-to-compare', REMOVE_STANDARD: 'remove-from-compare' } function reducer(standards, action) { switch(action.type) { case(ACTIONS.ADD_STANDARD): return [...standards, addCompare(action.payload.standard)] case(ACTIONS.REMOVE_STANDARD): return standards.filter(item => item.id !== action.payload.standard) default: return 'Nothing to add' } } function addCompare( standard ) { return axios .get("https://the-url.com" + standard) .then((res) => { console.log(res.data) return { key: res.data.id, title: res.data.title.rendered } }) .catch((err) => console.log(err)); } export default function EntryStandards() { const [standards, dispatch] = useReducer(reducer, []); const addStandards = function(id) { dispatch({ type: ACTIONS.ADD_STANDARD, payload: {standard: id}}) } return ( <> <button onClick={() => addStandards(9603)}>Add 9603!</button> <button onClick={() => addStandards(9567)}>Add 9567!</button> <button onClick={() => addStandards(9531)}>Add 9531!</button> <button onClick={() => addStandards(9519)}>Add 9519!</button> {standards.map(standard => { return <p><button onClick={() => dispatch({ type: ACTIONS.REMOVE_STANDARD, payload: { standard: standard.id } })}>X</button> { standard.title } - { standard.version }</p> })} </> ) }
问题核心原因
- reducer必须是纯函数:它只能同步处理state更新,不能包含任何异步操作(比如API请求、定时器)。你的代码在reducer里调用了
addCompare,这个函数返回的是Promise对象,而不是实际的数据,所以state里存的是Promise,不是你需要的标准对象。 - 渲染时无法读取Promise的属性:当你用
standards.map遍历的时候,每个standard是Promise,自然读不到title、id这些属性,看起来就像“空对象”。
优化实现方案
把异步请求移到组件的事件处理函数中,等请求完成后再dispatch action更新state:
修改后的代码
import React, { useReducer } from "react"; import axios from "axios"; export const ACTIONS = { ADD_STANDARD: 'add-to-compare', REMOVE_STANDARD: 'remove-from-compare' } // reducer保持纯函数,只处理同步的state更新 function reducer(standards, action) { switch(action.type) { case ACTIONS.ADD_STANDARD: // 这里直接接收请求好的数据,存入state return [...standards, action.payload.standard] case ACTIONS.REMOVE_STANDARD: return standards.filter(item => item.id !== action.payload.standardId) default: return standards } } // 单独封装API请求函数 async function fetchStandard(standardId) { try { const res = await axios.get(`https://the-url.com${standardId}`); return { id: res.data.id, // 修正字段名,确保删除时能匹配 title: res.data.title.rendered // 如果有version字段,这里补充对应取值逻辑 } } catch (err) { console.error('请求标准数据失败:', err); return null; } } export default function EntryStandards() { const [standards, dispatch] = useReducer(reducer, []); const addStandards = async function(id) { // 先请求数据 const standardData = await fetchStandard(id); // 请求成功再dispatch更新state if (standardData) { dispatch({ type: ACTIONS.ADD_STANDARD, payload: { standard: standardData } }); } } return ( <> <button onClick={() => addStandards(9603)}>Add 9603!</button> <button onClick={() => addStandards(9567)}>Add 9567!</button> <button onClick={() => addStandards(9531)}>Add 9531!</button> <button onClick={() => addStandards(9519)}>Add 9519!</button> {standards.map(standard => { return ( <p key={standard.id}> <button onClick={() => dispatch({ type: ACTIONS.REMOVE_STANDARD, payload: { standardId: standard.id } })}>X</button> { standard.title } {/* 如果有version字段,这里加上 { standard.version } */} </p> ) })} </> ) }
关键改动说明
- reducer回归纯函数:只负责接收已获取到的数据,同步更新state,不再处理任何异步逻辑。
- 异步请求移至事件处理函数:点击按钮后先调用API获取数据,拿到结果后再dispatch action。
- 修正数据字段一致性:把
key改成id,确保删除时能正确匹配;同时给列表项加上key属性,符合React列表渲染要求。 - 增加错误处理:请求失败时打印错误并避免存入无效数据。
可选优化:添加加载状态
如果请求耗时较长,可以给按钮添加加载状态,避免重复点击:
import React, { useReducer, useState } from "react"; // ... 其他代码不变 export default function EntryStandards() { const [standards, dispatch] = useReducer(reducer, []); const [loadingIds, setLoadingIds] = useState([]); const addStandards = async function(id) { if (loadingIds.includes(id)) return; setLoadingIds(prev => [...prev, id]); const standardData = await fetchStandard(id); setLoadingIds(prev => prev.filter(item => item !== id)); if (standardData) { dispatch({ type: ACTIONS.ADD_STANDARD, payload: { standard: standardData } }); } } return ( <> <button onClick={() => addStandards(9603)} disabled={loadingIds.includes(9603)} > {loadingIds.includes(9603) ? '加载中...' : 'Add 9603!'} </button> {/* 其他按钮同理修改 */} {/* 列表渲染部分不变 */} </> ) }
内容的提问来源于stack exchange,提问作者Ben
相关产品推荐
相关产品推荐

