useEffect 的依赖数组为空、有值、不传分别是什么效果?

三种情况

// 1. 不传依赖数组 → 每次渲染后都执行
useEffect(() => {
  console.log('每次渲染都执行')
})

// 2. 空数组 [] → 只在挂载后执行一次
useEffect(() => {
  console.log('只在挂载后执行一次')
}, [])

// 3. 有依赖值 [a, b] → 依赖变化时执行
useEffect(() => {
  console.log('a 或 b 变化时执行')
}, [a, b])

详细对比

依赖数组 执行时机 清理函数执行时机
不传 每次渲染后 下次 effect 执行前 + 卸载时
[] 挂载后一次 卸载时
[dep] 挂载后 + dep 变化时 下次 effect 执行前 + 卸载时

常见陷阱

1. 闭包陷阱(Stale Closure)

function Counter() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const timer = setInterval(() => {
      console.log(count) // ❌ 永远是 0(闭包捕获了初始值)
      setCount(count + 1) // ❌ 每次都设置成 1
    }, 1000)
    return () => clearInterval(timer)
  }, []) // count 不在依赖中

  // ✅ 解决方案 1:函数式更新
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(c => c + 1) // 使用函数式更新
    }, 1000)
    return () => clearInterval(timer)
  }, [])

  // ✅ 解决方案 2:添加依赖
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(count + 1)
    }, 1000)
    return () => clearInterval(timer)
  }, [count]) // 每次都清除并重建定时器
}

2. 对象/数组依赖

// ❌ 每次渲染都是新对象,effect 每次都执行
useEffect(() => { /* ... */ }, [{ a: 1 }])

// ✅ 使用原始值
useEffect(() => { /* ... */ }, [obj.a])

// ✅ 或用 useMemo
const dep = useMemo(() => ({ a: 1 }), [a])
useEffect(() => { /* ... */ }, [dep])

3. 函数依赖

// ❌ 每次渲染都是新函数
useEffect(() => { handleClick() }, [handleClick])

// ✅ 用 useCallback 包裹
const handleClick = useCallback(() => { /* ... */ }, [dep])
useEffect(() => { handleClick() }, [handleClick])

ESLint 规则

react-hooks/exhaustive-deps 会自动检测缺失的依赖,建议开启。

同分类其他题目