useEffect 是 React Hooks 的基石,用于处理副作用(数据获取、订阅、手动操作 DOM 等)。但它的依赖数组 [] 常成为混乱之源:遗漏依赖导致状态陈旧,过度依赖引发无限循环。如何精准掌控?核心在于 理解依赖的必要性和完整性。
常见陷阱:
遗漏依赖: 在 Effect 内部使用了 state 或 prop 值,却未将其放入依赖数组。这会导致 Effect 回调“看到”的是其首次创建时的旧值,而非最新值。
const [count, setCount] = useState(0); useEffect(() => { // 🚫 依赖缺失:点击按钮后,interval 内 count 永远是 0 const id = setInterval(() => console.log(count), 1000); return () => clearInterval(id); }, []); // 空依赖过度依赖(无限循环): 将 Effect 内部 修改 的状态变量放入依赖数组。Effect 执行 -> 修改状态 -> 依赖变化 -> Effect 再次执行 -> 循环往复。
const [data, setData] = useState(null); useEffect(() => { fetchData().then(res => setData(res)); }, [data]); // 🚫 依赖了会被修改的 data -> 无限循环
解决之道:依赖完整性原则
诚实声明依赖: 所有在 Effect 回调内部使用到的、来自组件作用域的值(props, state, context, 及其衍生的值),都必须包含在依赖数组中。 这是 React 的硬性规则。使用 ESLint 插件(如
eslint-plugin-react-hooks)能强制此规则,避免遗漏。避免修改依赖导致循环:
- 空依赖 (
[]): 仅需在组件挂载/卸载时运行一次的 Effect(如初始化订阅、事件监听)。确保回调内不依赖任何 props/state。 - 函数式更新: 当 Effect 需要基于 前一个状态 更新状态时,使用函数式更新,这样就不需要将该状态放入依赖。
useEffect(() => { const id = setInterval(() => { setCount(prevCount => prevCount + 1); // ✅ 使用 prevCount,无需依赖 count }, 1000); return () => clearInterval(id); }, []); // 空依赖安全 - 将函数移入 Effect 或
useCallback: 如果依赖的函数在渲染中定义且会变,将其移入 Effect 内部定义,或用useCallback包裹并指定其依赖,确保引用稳定。
- 空依赖 (
总结:
精准管理 useEffect 依赖的关键在于:
- 完整性: 依赖数组必须包含 Effect 内部使用的所有可变值。
- 稳定性: 避免依赖在每次渲染都变化的引用(如内联函数、对象),必要时使用
useCallback/useMemo。 - 利用工具: 开启 ESLint 规则 (
exhaustive-deps) 强制依赖检查。
遵循这些原则,你就能驯服 useEffect,写出高效、可预测的副作用代码!