React中循环调用PokeApi时数据异常的问题求助
问题:循环调用PokeApi获取宝可梦数据不完整、返回不稳定
拥有一份宝可梦名称列表,需要循环调用PokeApi获取数据并展示,但输出数据不完整,返回值不稳定,怀疑是异步同步或数据覆盖问题导致,尝试多种方法未解决。相关代码如下:
原代码
App.tsx
import { gen1List } from 'data'; import React from 'react'; import { Pokemon } from 'models'; import { useFetchPokemon } from 'api/useFetchPokemon'; export const App = () => { const gen1Pokemon = useFetchPokemon(gen1List); return ( <> {gen1Pokemon.map((pokemon: Pokemon, index: number) => ( <div key={index}> {pokemon.name} <img src={pokemon.sprite} alt={pokemon.name} /> </div> ))} </> ); };
useFetchPokemon.ts
import { Pokemon, filterPokemonData } from "models"; import { useEffect, useState } from "react" import { fetchPokemon } from "api/fetchPokemon"; export const useFetchPokemon = (pokemonList: string[]) => { const [pokemon, setPokemon] = useState<Pokemon[]>([]); const pokemonArray: Pokemon[] = []; useEffect(() => { for (const mon of pokemonList) { fetchPokemon(mon).then((result) => { pokemonArray.push(filterPokemonData(result)); }); }; setPokemon(pokemonArray); }, []); return pokemon; };
fetchPokemon.ts
export const fetchPokemon = async (name: string): Promise<any> => { const response = await fetch('https://pokeapi.co/api/v2/pokemon/' + name); return await response.json(); };
models.ts
export interface Pokemon { name: string; sprite: string; }; export const filterPokemonData = (pokemon): Pokemon => { return { name: pokemon.name, sprite: pokemon.sprites.front_default, } };
data.ts
export const gen1List: string[] = [ 'bulbasaur', 'venusaur', 'charizard', ];
问题根源
- 异步时序错误:
useFetchPokemon中,循环发起异步请求后立刻调用setPokemon(pokemonArray),此时所有fetchPokemon请求都还未返回,pokemonArray是空数组;后续请求返回后修改数组也不会触发状态更新。 - 闭包与状态更新逻辑问题:
pokemonArray是useEffect内部的局部变量,请求返回后向其中push数据不会触发组件重新渲染;且setPokemon仅执行一次,后续数组变化无法同步到状态中。 - 依赖项缺失:
useEffect的依赖数组为空,当pokemonList变化时不会重新触发请求,同时违反React Hook的依赖规则。
解决方案
核心思路是用Promise.all等待所有异步请求完成后统一更新状态,同时修正依赖项,避免闭包问题:
修改后的useFetchPokemon.ts
import { Pokemon, filterPokemonData } from "models"; import { useEffect, useState } from "react" import { fetchPokemon } from "api/fetchPokemon"; export const useFetchPokemon = (pokemonList: string[]) => { const [pokemon, setPokemon] = useState<Pokemon[]>([]); const [isLoading, setIsLoading] = useState(true); useEffect(() => { const fetchAllPokemon = async () => { setIsLoading(true); try { // 并行发起所有请求,统一处理结果 const pokemonPromises = pokemonList.map(mon => fetchPokemon(mon).then(filterPokemonData) ); const fetchedPokemon = await Promise.all(pokemonPromises); setPokemon(fetchedPokemon); } catch (error) { console.error("获取宝可梦数据失败:", error); } finally { setIsLoading(false); } }; fetchAllPokemon(); }, [pokemonList]); // 添加列表到依赖,确保列表变化时重新请求 return { pokemon, isLoading }; };
对应优化后的App.tsx
import { gen1List } from 'data'; import React from 'react'; import { Pokemon } from 'models'; import { useFetchPokemon } from 'api/useFetchPokemon'; export const App = () => { const { pokemon: gen1Pokemon, isLoading } = useFetchPokemon(gen1List); if (isLoading) { return <div>加载中...</div>; } return ( <> {gen1Pokemon.map((pokemon: Pokemon) => ( <div key={pokemon.name}> {/* 用唯一name作为key,避免index带来的渲染问题 */} {pokemon.name} <img src={pokemon.sprite} alt={pokemon.name} /> </div> ))} </> ); };
关键优化点
- 使用
Promise.all并行处理所有请求,确保所有数据返回后再更新状态,彻底解决数据不完整问题。 - 将请求逻辑封装在异步函数内,用
await保证时序正确,避免闭包导致的状态不同步。 - 添加
pokemonList到useEffect依赖数组,符合React Hook规则,同时支持列表动态变化时重新请求。 - 新增加载状态与错误捕获,提升用户体验与代码健壮性。
- 替换
key为宝可梦名称,避免使用index带来的组件重渲染错误。
内容的提问来源于stack exchange,提问作者Alex Brodnicki
相关产品推荐
相关产品推荐

