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

如何解决React useEffect调用PokeAPI时的无限循环问题

解决方案:修复无限循环与优化性能

问题根源

  1. 无限循环原因:你的useEffect将myPokemonsList设为依赖,但内部的getAllPokemons函数会调用setMyPokemonsList更新该状态,导致依赖变化后再次触发useEffect,形成无限循环。
  2. 冗余异步操作:filterMyPokemons标记为async,但Array.filter是同步方法,await完全多余,反而可能引发不必要的异步逻辑。
  3. 不必要的状态存储:pokemonListFiltered属于派生状态(可由pokemonList和myPokemonsList计算得出),单独存储会增加状态同步成本,还可能导致数据不一致。

具体修复步骤

1. 重构useEffect,消除无限循环

将useEffect的依赖设为空数组,仅在组件挂载时执行一次数据获取;同时用Promise.all并行请求,提升加载效率:

useEffect(() => { 
  const fetchData = async () => {
    setLoading(true);
    // 并行请求,减少等待时间
    const [myPokemons, pokemon] = await Promise.all([
      showMyPokemons(),
      api.getAllPokemon()
    ]);
    setPokemonList(pokemon);
    setMyPokemonsList(myPokemons);
    setLoading(false); 
  }
  fetchData();    
}, []); // 空依赖,仅组件挂载时执行

2. 用useMemo派生过滤列表,替代状态存储

删除pokemonListFiltered状态和filterMyPokemons函数,改用useMemo缓存过滤结果,避免重复计算:

// 过滤掉已捕获的宝可梦
const pokemonListFiltered = useMemo(() => {
  return pokemonList.filter(pokemon => !myPokemonsList.includes(pokemon.name));
}, [pokemonList, myPokemonsList]); // 仅当依赖变化时重新计算

// 搜索过滤也用useMemo缓存
const filteredPokemons = useMemo(() => {
  const lowerSearch = text.toLowerCase(); 
  return pokemonListFiltered.filter(pokemon => 
    pokemon.name.toLowerCase().includes(lowerSearch)    
  );
}, [pokemonListFiltered, text]);

3. (可选)监听myPokemons实时变化

如果你的CatchContext中直接暴露了myPokemons状态(而非仅通过showMyPokemons函数获取),可以将其加入useEffect依赖,实现实时更新已捕获列表:

// 假设Context提供myPokemons状态
const { myPokemons } = useContext(CatchContext);

// 修改useEffect依赖
useEffect(() => { 
  const fetchData = async () => {
    setLoading(true);
    const pokemon = await api.getAllPokemon();
    setPokemonList(pokemon);
    setMyPokemonsList(myPokemons);
    setLoading(false); 
  }
  fetchData();    
}, [myPokemons]); // 当myPokemons变化时重新获取数据(如果需要)

修改后的完整代码

import * as C from './styles';

import logo from '../../assets/pokelogo.png';
import { useContext, useState, useEffect, useMemo } from 'react';
import { useApi } from '../../hooks/useApi';
import { useNavigate } from 'react-router-dom';

import Pokelist from '../../components/PokeList';

import CatchingPokemonIcon from '@mui/icons-material/CatchingPokemon';
import CatchContext from '../../context/Context';


const Homepage = () => {
    const api = useApi();
    const { showMyPokemons } = useContext(CatchContext);
    const navigate = useNavigate();

    const [pokemonList, setPokemonList] = useState([]);
    const [loading, setLoading] = useState(false);
    const [text, setText] = useState('');
    const [myPokemonsList, setMyPokemonsList] = useState([]);

    useEffect(() => { 
        const fetchData = async () => {
            setLoading(true);
            const [myPokemons, pokemon] = await Promise.all([
                showMyPokemons(),
                api.getAllPokemon()
            ]);
            setPokemonList(pokemon);
            setMyPokemonsList(myPokemons);
            setLoading(false); 
        }
        fetchData();    
    }, []); 

    // 派生过滤后的列表
    const pokemonListFiltered = useMemo(() => {
        return pokemonList.filter(pokemon => !myPokemonsList.includes(pokemon.name));
    }, [pokemonList, myPokemonsList]);

    // 搜索过滤
    const filteredPokemons = useMemo(() => {
        const lowerSearch = text.toLowerCase(); 
        return pokemonListFiltered.filter(pokemon => 
            pokemon.name.toLowerCase().includes(lowerSearch)    
        );
    }, [pokemonListFiltered, text]);

    const handleHome = () => {
        navigate('/')
    }

    const handleMyPokemons = () => {
        navigate('/mypokemons')
    }


  return (
    <C.Container>
        <C.Logo>
            <img src={logo} alt="宝可梦Logo" />
        </C.Logo>
        <C.Navbar>
            <input 
                type="text" 
                placeholder='Busque um pokémon...'
                onChange={(e) => setText(e.target.value)}
                value={text}
            />
        </C.Navbar>
        <C.Pokedatabase onClick={handleMyPokemons}>
            <button>Meus pokémons <i><CatchingPokemonIcon /></i></button>
        </C.Pokedatabase>
        {loading ? (
            <div>加载中...</div>
        ) : (
            <C.Pokelist>
                {filteredPokemons.map(pokemon => (
                    <Pokelist 
                        key={pokemon.name} // 添加key,避免React警告
                        name={pokemon.name}
                    />
                ))}
            </C.Pokelist>
        )}
    </C.Container>
  )
}

export default Homepage;

额外优化点

  • 添加了loading状态的渲染逻辑,提升用户体验
  • 给map循环的元素添加了key,消除React控制台警告
  • 用Promise.all并行请求,减少数据加载等待时间

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 14:45:36