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

如何在React中用useEffect从PokeAPI通过已获JSON的URL二次取数?

解决React循环中无法调用自定义useFetch钩子获取宝可梦详情的问题

React的钩子规则明确要求不能在循环、条件判断或嵌套函数中调用钩子,因为React依赖钩子的调用顺序来管理组件状态。针对你的需求,这里提供两种实用的解决方式:

方法一:将详情请求封装到独立子组件

这是最贴合React设计思想的方案——把单个宝可梦的详情请求与渲染逻辑拆分成独立的子组件,让子组件在自身顶层调用useFetch钩子,完全符合规则。

子组件 PokeDetailCard.jsx

import useFetch from './useFetch';

function PokeDetailCard({ pokemonUrl, name }) {
  // 在子组件顶层调用useFetch,符合钩子规则
  const { data: pokemonDetail, loading, error } = useFetch(pokemonUrl);

  if (loading) return <div className="poke-card">加载中...</div>;
  if (error) return <div className="poke-card">加载失败:{error.message}</div>;

  return (
    <div className="poke-card">
      <h3>{name}</h3>
      <p>身高:{pokemonDetail.height}</p>
      <p>体重:{pokemonDetail.weight}</p>
      <img 
        src={pokemonDetail.sprites.front_default} 
        alt={name}
        className="poke-img"
      />
    </div>
  );
}

export default PokeDetailCard;

父组件中循环渲染子组件

import useFetch from './useFetch';
import PokeDetailCard from './PokeDetailCard';

function PokemonGrid() {
  const { data: pokemonList, loading, error } = useFetch('https://pokeapi.co/api/v2/pokemon?limit=9');

  if (loading) return <div>加载宝可梦列表中...</div>;
  if (error) return <div>列表加载失败:{error.message}</div>;

  return (
    <div className="poke-grid">
      {pokemonList.results.map((pokemon) => (
        <PokeDetailCard
          key={pokemon.name}
          pokemonUrl={pokemon.url}
          name={pokemon.name}
        />
      ))}
    </div>
  );
}

export default PokemonGrid;

这种方式还能实现每个卡片独立的加载/错误状态,用户体验更友好。

方法二:在父组件中批量请求所有详情数据

如果希望一次性加载所有宝可梦的详情,而非每个卡片单独发起请求,可以在父组件中通过useEffect配合Promise.all批量处理请求。

import { useState, useEffect } from 'react';
import useFetch from './useFetch';

function PokemonGrid() {
  const { data: pokemonList, loading: listLoading, error: listError } = useFetch('https://pokeapi.co/api/v2/pokemon?limit=9');
  const [pokemonDetails, setPokemonDetails] = useState([]);
  const [detailsLoading, setDetailsLoading] = useState(false);
  const [detailsError, setDetailsError] = useState(null);

  useEffect(() => {
    // 列表未加载完成时不执行请求
    if (!pokemonList?.results) return;

    setDetailsLoading(true);
    setDetailsError(null);

    // 批量请求所有宝可梦详情
    Promise.all(
      pokemonList.results.map(pokemon => 
        fetch(pokemon.url).then(res => res.json())
      )
    )
    .then(details => {
      setPokemonDetails(details);
      setDetailsLoading(false);
    })
    .catch(err => {
      setDetailsError(err);
      setDetailsLoading(false);
    });
  }, [pokemonList]);

  if (listLoading) return <div>加载列表中...</div>;
  if (listError) return <div>列表加载失败:{listError.message}</div>;
  if (detailsLoading) return <div>加载详情中...</div>;
  if (detailsError) return <div>详情加载失败:{detailsError.message}</div>;

  return (
    <div className="poke-grid">
      {pokemonDetails.map((detail) => (
        <div key={detail.name} className="poke-card">
          <h3>{detail.name}</h3>
          <p>身高:{detail.height}</p>
          <p>体重:{detail.weight}</p>
          <img src={detail.sprites.front_default} alt={detail.name} />
        </div>
      ))}
    </div>
  );
}

export default PokemonGrid;

如果想复用你的useFetch钩子,也可以把批量请求逻辑封装成新的自定义钩子(比如useBatchFetch),核心思路还是通过Promise.all统一处理多个请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 07:02:35