在 React 函数组件中,直接在组件主体内发起 Axios 请求会导致每次状态更新(如 setProgress)触发重渲染,进而反复执行请求——使用 useEffect 并传入空依赖数组 [],可确保初始化请求仅执行一次。
在 react 函数组件中,直接在组件主体内发起 axios 请求会导致每次状态更新(如 `setprogress`)触发重渲染,进而反复执行请求——使用 `useeffect` 并传入空依赖数组 `[]`,可确保初始化请求仅执行一次。
React 函数组件的执行逻辑与类组件有本质区别:组件函数本身不是“生命周期钩子”,而是每次渲染都会完整执行的普通 JavaScript 函数。因此,像原始代码中那样在函数体顶层调用 axios.get(...),等同于把副作用(网络请求)写在了渲染逻辑里——一旦 setProgress 或 setAllData 触发状态更新,组件就会重新执行,再次发起请求,形成恶性循环,最终导致浏览器卡死。
✅ 正确解法:将数据获取逻辑移入 useEffect,并明确声明其执行时机:
import './App.css';
import axios from 'axios';
import React, { useState, useEffect } from 'react';
function App() {
const [allDataJson, setAllData] = useState(null);
const [progress, setProgress] = useState(0);
useEffect(() => {
// ✅ 副作用封装:仅在组件挂载时执行一次
const fetchData = async () => {
try {
// 第一步:获取 Pokémon 列表(分页合并)
let allResults = [];
let url = "https://pokeapi.co/api/v2/pokemon/?offset=0&limit=20";
while (url) {
const response = await axios.get(url);
allResults = [...allResults, ...response.data.results];
url = response.data.next; // 自动处理分页,避免 do-while + await 混用风险
}
// 第二步:并发获取详情(推荐 Promise.all 提升性能)
const detailPromises = allResults.map(async (item, index) => {
const detail = await getDetails(item.url);
setProgress(prev => prev + 1); // ✅ 安全更新:使用函数式更新避免闭包旧值
return { ...item, detail };
});
const fullData = await Promise.all(detailPromises);
setAllData(JSON.stringify(fullData, null, 2)); // 格式化输出便于调试
} catch (error) {
console.error("数据加载失败:", error);
}
};
fetchData();
}, []); // ? 空依赖数组:确保只在组件首次挂载时运行
return (
<div classname="app">
{allDataJson ? (
<div>
<h2>✅ 加载完成!共 {JSON.parse(allDataJson).length} 只宝可梦</h2>
<pre class="brush:php;toolbar:false;">{allDataJson}











