
在 react 函数组件中,直接在渲染体中执行异步操作(如 fetch)会导致重复请求、内存泄漏和状态更新异常,必须使用 useeffect 进行正确封装。
在 react 函数组件中,直接在渲染体中执行异步操作(如 fetch)会导致重复请求、内存泄漏和状态更新异常,必须使用 useeffect 进行正确封装。
你提供的代码看似“能运行”,但存在严重隐患:每次组件重渲染(例如点击按钮触发 setNum、父组件更新、或 React 内部调度),fetch 都会被无条件重新执行——这不仅浪费网络资源,更可能引发竞态问题(race condition),导致旧请求的响应覆盖新状态,显示错误的数据。
✅ 正确做法是将数据获取逻辑移入 useEffect,并明确声明依赖项:
import { useState, useEffect } from 'react';
export default function App() {
const [title, setTitle] = useState(null);
const [num, setNum] = useState(2);
useEffect(() => {
fetch(`https://jsonplaceholder.typicode.com/todos/${num}`)
.then((response) => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
})
.then((data) => {
setTitle(data.title); // 注意:解构前确保 data 存在且有 title 属性
})
.catch((error) => {
console.error('Failed to fetch todo:', error);
setTitle('Failed to load');
});
}, [num]); // ✅ 仅当 num 变化时触发请求
return (
<div className="App">
<h1>useEffect Example</h1>
<p>{title ?? 'Loading...'}</p>
<button onClick={() => setNum(prev => prev + 1)}>Increment</button>
</div>
);
}⚠️ 关键注意事项:
- 依赖数组 [num] 不可省略:若遗漏,useEffect 将退化为 componentDidMount + componentDidUpdate,仍会重复执行;若写成空数组 [],则只在挂载时执行一次,无法响应 num 更新。
- 添加错误处理与加载状态:真实场景中应处理网络失败、空响应等边界情况,并提供用户反馈(如 loading 提示)。
- 避免 setState 在卸载组件后执行:更健壮的实现可结合 AbortController 或自定义清理函数防止内存泄漏(尤其在快速切换 num 时)。
? 总结:是否“小”不是判断标准——只要逻辑涉及副作用(数据获取、订阅、DOM 操作等),就必须用 useEffect 封装。这是 React 函数组件的规则,而非可选优化。跳过它,短期看似可行,长期必然引入难以调试的 bug。


















