useEffect:副作用、依赖数组与清理函数
知识背景
纯渲染(根据 state/props 算出 UI)之外的操作——请求接口、订阅事件、改 document.title、定时器——在 React 里称为副作用,通常放在 useEffect 里,避免阻塞渲染本身。
Class 时代对应 componentDidMount / componentDidUpdate / componentWillUnmount;useEffect 把「挂载后 / 依赖变了 / 卸载前」统一成一套 API,但依赖写错是线上 bug 第一大来源。
知识详解与通俗解释
1. 三种常见写法
jsx
// 挂载 + 每次更新后都会跑(慎用,易死循环或多余请求)
useEffect(() => { ... });
// 仅挂载时跑一遍(第二个参数为空数组)
useEffect(() => { ... }, []);
// 当 a 或 b 变化时跑
useEffect(() => { ... }, [a, b]);通俗说:React 在「提交 DOM 之后」异步执行 effect;依赖数组告诉 React 哪些外部值变了才需要重新执行这份副作用。
2. 清理函数:return 一个函数
jsx
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []);卸载组件或依赖变化导致重新执行 effect 之前,会先跑上一次 effect 返回的清理函数。适合:取消订阅、清定时器、abort fetch。
3. 依赖原则(eslint-plugin-react-hooks)
- effect 体内用到的、来自组件作用域的响应式值(props、state、以及若变化应重跑的函数)一般都要进依赖数组。
- 用
useCallback/useMemo稳定引用,是为了正确收窄依赖,而不是为了骗过 linter。 - 若故意省略依赖:要么抽逻辑到子组件,要么用 ref 保存「仅读最新、不参与渲染」的值,并清楚自己在做什么。
4. useEffect vs useLayoutEffect(预告)
两者触发时机不同:useEffect 在浏览器绘制之后;useLayoutEffect 在 DOM 更新后、绘制之前同步执行。测量布局、避免闪烁时用后者;默认用前者减少阻塞。
总结
useEffect管副作用;依赖数组 = 这份副作用与谁同步。- 清理函数防止内存泄漏与重复订阅。
- 遵守 exhaustive-deps 能避免大量「请求没重发、订阅的是旧 id」类 bug。