如何让使用useReducer的React组件在axios调用完成后正确重渲染
问题根源
你遇到的问题本质是异步操作时序处理错误,所有尝试的方案都存在同一个问题:没有等待接口请求返回完整数据,就提前把未赋值的空Card对象提交到了状态里。
接口请求是异步执行的,你初始化空Card对象后直接返回/resolve,此时接口还没返回结果,拿到的永远是空对象。等后续接口请求完成修改Card对象时,React状态已经更新完成,不会自动响应这个对象的内部属性变化,所以会先显示空卡片,等到下一次状态更新触发重渲染时,之前修改过属性的Card才会正常显示,同时又会新增一个刚提交的空Card,形成你遇到的循环。
修复方案
1. 正确封装异步请求方法
用Promise.all等待两个接口都返回结果,组装好完整的Card对象后再返回,用async/await写法更易读:
// index.tsx const getRandomPokemon = async (): Promise<Card> => { const randomInt = String(Math.floor(898 * Math.random())); // 并行请求两个接口,全部返回后再往下执行 const [cardData, speciesData] = await Promise.all([ PokemonDataService.getCard(randomInt), PokemonDataService.getSpecies(randomInt) ]); // 补全你之前省略的Card属性赋值逻辑,示例如下: const newCard: Card = { name: cardData.data.name, image: cardData.data.sprites.other["official-artwork"].front_default, // 其他从species接口取的属性 }; return newCard; };
2. 调整点击事件逻辑,等待数据返回后再更新状态
const App = (props: AppProps) => { const [deck, dispatch] = useReducer(cardReducer, initialState); const addCard = async () => { try { // 等待接口返回完整数据后再dispatch更新状态 const newCard = await getRandomPokemon(); dispatch({ type: ActionKind.Add, payload: newCard }); } catch (err) { console.log(`获取宝可梦失败:\n ${err}`); } }; return ( <div> <Deck deck={deck} /> <CatchButton onClick={addCard}>捕捉新宝可梦</CatchButton> </div> ); };
3. 可选优化项
简化reducer逻辑
不需要额外遍历克隆原有状态,直接展开数组即可,性能更好:
// cardReducer.tsx export function cardReducer(state: Card[], action: Action): Card[] { switch (action.type) { case ActionKind.Add: return [...state, action.payload]; default: return state; } }
渲染层兼容处理
加可选链避免属性不存在时报错,也可以加加载状态提示:
// Deck.tsx export const Deck = ({ deck }: DeckProps) => { return ( <PokeDeck> {deck.map((card, index) => ( <PokeCard key={index}> <img src={card?.image} alt={`${card?.name || '宝可梦'}的图片`} /> <h2>{card?.name || '加载中...'}</h2> </PokeCard> ))} </PokeDeck> ); };
内容的提问来源于stack exchange,提问作者Mac Bubb
相关产品推荐
相关产品推荐

