React+TypeScript Pokedex应用宝可梦渲染顺序错乱问题求助
问题分析
你的猜测完全正确——问题出在并发请求的返回顺序不固定。当前代码中,你循环调用mapPokemon发起每个宝可梦的详情请求,这些请求并行执行,哪个请求先拿到响应,对应的宝可梦就会先被追加到pokedex数组,导致刷新后顺序错乱。另外你误用了Promise.all(data):data是普通的宝可梦列表数组,不是Promise数组,所以Promise.all直接resolve了原数组,并没有等待所有详情请求完成。
解决方案
推荐两种可靠的修复方式:
方法1:并发请求后统一排序(效率优先)
先并行发起所有详情请求,拿到全部数据后按宝可梦的id排序,再一次性更新状态。既保留并发请求的效率,又能保证顺序正确:
import { useEffect, useState } from "react"; import "./App.css"; import axios from "axios"; import { PokemonType } from "./models/pokemonType"; import Pokemon from "./components/Pokemon"; function App() { const [pokedex, setPokedex] = useState<PokemonType[] | []>([]); const [loading, setLoading] = useState(true); const limit = 151; const axiosInstance = axios.create({ baseURL: "https://pokeapi.co/api/v2/", }); useEffect(() => { collectPokemon(limit); }, []); const collectPokemon = async (limit: number) => { try { // 1. 获取宝可梦基础列表 const listRes = await axiosInstance.get(`pokemon?limit=${limit}`); const pokemonList = listRes.data.results; // 2. 生成所有详情请求的Promise数组 const detailPromises = pokemonList.map(pkmn => axiosInstance.get(`pokemon/${pkmn.name}`) ); // 3. 等待所有请求完成 const detailResponses = await Promise.all(detailPromises); // 4. 提取数据并按id排序 const sortedPokemon = detailResponses .map(res => res.data as PokemonType) .sort((a, b) => a.id - b.id); // 5. 一次性更新状态 setPokedex(sortedPokemon); } catch (err) { console.error("请求出错:", err); } finally { setLoading(false); } }; return ( <> <h1>Pokédex App</h1> {loading ? ( <div className="flex justify-center items-center h-screen">加载中...</div> ) : ( <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-10 m-10"> {pokedex.map((mon: PokemonType) => ( <Pokemon key={mon.id} name={mon.name} id={mon.id} types={mon.types} /> ))} </div> )} </> ); } export default App;
方法2:串行请求(顺序严格但效率低)
如果必须严格按列表顺序发起请求,可以用for...of循环逐个等待请求完成,但总加载时间会显著增加,不推荐用于大量数据:
const collectPokemon = async (limit: number) => { try { const listRes = await axiosInstance.get(`pokemon?limit=${limit}`); const pokemonList = listRes.data.results; const newPokedex: PokemonType[] = []; for (const pkmn of pokemonList) { const detailRes = await axiosInstance.get(`pokemon/${pkmn.name}`); newPokedex.push(detailRes.data as PokemonType); } setPokedex(newPokedex); } catch (err) { console.error("请求出错:", err); } finally { setLoading(false); } };
额外修复点
- Pokemon组件中
key={Math.random()}会导致组件频繁不必要重渲染,改为用类型名作为key:
<div className="flex flex-row fixed right-2 top-16 space-x-3"> {types?.map((item) => ( <div key={item.type.name} className="bg-red-500 rounded-lg p-2 capitalize border-gray-950 border-2 "> <p>{item.type?.name}</p> </div> ))} </div>
- 新增的
loading状态可以避免页面初始为空的尴尬,提升用户体验。
内容的提问来源于stack exchange,提问作者dtgaeh
相关产品推荐
相关产品推荐

