You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
  }
};
额外修复点
  1. 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>
  1. 新增的loading状态可以避免页面初始为空的尴尬,提升用户体验。

内容的提问来源于stack exchange,提问作者dtgaeh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 13:52:52