「副作用」= 不属于纯渲染的操作:发起请求、监听事件、开定时器、改 document.title。useEffect(() => { ... }) 的回调在渲染提交到屏幕之后执行(不阻塞渲染),这是它与 useLayoutEffect 的区别。
依赖数组是它的全部学问:不写数组 → 每次渲染后都执行;写 [x] → 仅 x 变化时执行;写 [] → 仅挂载时执行一次。清理函数 return () => {...} 在组件卸载或下次副作用重跑前调用——定时器/订阅必须在清理函数里拆掉,否则内存泄漏。请求竞态是经典坑:组件卸载后请求返回还 setState 会报警告,用清理函数里的 ignore 标志挡住。
function User({ id }) {
const [data, setData] = useState(null);
useEffect(() => {
let ignore = false; // ★ 竞态标志
fetch('/api/user/' + id)
.then(r => r.json())
.then(d => { if (!ignore) setData(d); }); // 卸载后不更新
return () => { ignore = true; }; // 清理:id 变化/卸载时
}, [id]); // ★ 依赖 id
useEffect(() => {
const t = setInterval(() => console.log('tick'), 1000);
return () => clearInterval(t); // ★ 必须清,否则泄漏
}, []);
return data ? <p>{data.name}</p> : <p>加载中…</p>;
}