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

React中useEffect调用函数为何生成重复Pokemon数据?

问题原因与解决方案

核心原因

React 18及以上版本的开发模式中,StrictMode会自动重复执行无依赖的useEffect回调(以及其他部分生命周期函数),目的是帮你检测潜在的副作用bug。你的代码里:

  • useEffect依赖为空数组,所以在开发环境会被执行两次
  • 每次执行都会调用getAllPokemons,而第一次调用时loadPoke还是初始的https://pokeapi.co/api/v2/pokemon?limit=20,两次调用都会拉取前20个Pokemon并添加到allPokemons数组,直接导致重复项。

而按钮触发时,每次点击只执行一次getAllPokemons,且第一次执行后loadPoke已经被更新为下一页的接口地址,后续点击只会添加新的Pokemon,不会重复。

另外你的getAllPokemons里还有个潜在问题:用forEach遍历异步操作,forEach不会等待异步任务完成,会导致多个setAllPokemons被无序调用,即使没有StrictMode的问题,也可能出现数据顺序混乱或不必要的重渲染。

修复方案

方案1:添加初始化标记,阻止重复执行

添加一个状态标记是否已经完成初始加载,避免useEffect重复触发时再次调用接口:

const PokeListPage = () => {
  const [layoutToggle, setLayoutToggle] = useState(false);
  const [allPokemons, setAllPokemons] = useState([]);
  const [loadPoke, setLoadPoke] = useState(
    "https://pokeapi.co/api/v2/pokemon?limit=20"
  );
  // 添加初始化加载标记
  const [isInitLoaded, setIsInitLoaded] = useState(false);

  useEffect(() => {
    if (!isInitLoaded) {
      getAllPokemons();
      setIsInitLoaded(true);
    }
  }, [isInitLoaded]);

  const getAllPokemons = async () => {
    const res = await fetch(loadPoke);
    const data = await res.json();
    setLoadPoke(data.next);

    // 改用Promise.all批量获取,解决forEach异步遍历的问题
    const pokemonDataList = await Promise.all(
      data.results.map(async (pokemon) => {
        const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${pokemon.name}`);
        return res.json();
      })
    );

    // 一次性更新数组,减少重渲染次数
    setAllPokemons(prev => [...prev, ...pokemonDataList]);
  };

  // 其他组件逻辑...
};

方案2:临时禁用StrictMode(仅测试用)

如果只是临时验证问题,可以在项目入口文件(比如index.js)中去掉<StrictMode>的包裹,但不推荐长期使用——StrictMode能帮你提前发现很多隐蔽的bug。

额外说明

用Promise.all替代forEach异步遍历的好处:

  • 所有请求并行发起,提升加载速度
  • 等待所有请求完成后一次性更新状态,减少组件重渲染次数
  • 保证添加到数组的Pokemon顺序和接口返回的一致

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 14:10:35