在 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>{allDataJson}</pre>
</div>
) : (
<div>
<h3>⏳ 加载中,请稍候...</h3>
<div>进度:{progress} / 20</div>
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${Math.min(100, (progress / 20) * 100)}%` }}
/>
</div>
</div>
)}
</div>
);
}
// ✅ 优化缓存实现(修复原版潜在内存泄漏 & 逻辑错误)
const cache = new Map();
export async function getDetails(url) {
const id = url.match(/\/(\d+)\/$/)?.[1]; // 更健壮地提取 ID
if (!id) throw new Error(`Invalid URL: ${url}`);
if (cache.has(id)) return cache.get(id);
const response = await axios.get(url);
cache.set(id, response.data);
return response.data;
}
export default App;? 关键注意事项:
- 永远不要在组件顶层(render body)直接调用异步请求或 setState —— 这是 React 新手最常见误区;
- useEffect(fn, []) 是「组件级初始化」的标准模式,等价于 componentDidMount;
- 使用 Promise.all() 并发拉取详情,比 for...of + await 串行快数十倍(20 个请求从 ~20s → ~1s);
- setProgress(prev => prev + 1) 使用函数式更新,避免因闭包捕获过期 progress 值导致进度跳变;
- 缓存建议用 Map 替代普通对象,避免原型污染与类型安全问题;
- 实际项目中应添加 Loading 状态、错误边界(ErrorBoundary)和取消请求机制(如 AbortController),本例为教学简化。
通过 useEffect 明确分离「渲染」与「副作用」,你就能写出稳定、可预测、符合 React 思维模型的数据加载逻辑。


















