如何在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
相关产品推荐
相关产品推荐

