You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Hook React.useEffect条件调用报错的原因及修复方案

React Hook 条件调用报错原因及修复方案

报错原因

你遇到的react-hooks/rules-of-hooks报错,核心是违反了React Hooks的强制调用规则:

  1. Hooks必须在组件的顶层作用域调用,不能嵌套在条件判断、循环、内部函数中
  2. 每次组件渲染时,Hooks的调用顺序必须完全一致

看你的代码,问题出在两点:

  • 自定义Hook useScrollDirection被定义在了组件内部,属于嵌套函数
  • 该Hook内部的React.useEffect被包裹在if (typeof window !== 'undefined')条件判断里,导致不同渲染周期中,这个Effect的调用顺序可能变化,触发React的Hook规则校验报错

修复方法

1. 规范自定义Hook的定义与调用

自定义Hook必须定义在组件外部,同时把条件判断移到Effect内部,避免包裹Hook调用:

// 把自定义Hook移到组件外部,保证顶层作用域调用
const useScrollDirection = () => {
  const [scrollDirection, setScrollDirection] = React.useState('')
  const [prevOffset, setPrevOffset] = React.useState(0)

  React.useEffect(() => {
    // 在Effect内部判断window是否存在,适配SSR环境
    if (typeof window === 'undefined') return

    const nav = document.querySelector('.nav');
    const toggleScrollDirection = () => {
      const scrollY = window.scrollY
      if (scrollY === 0) {
        setScrollDirection('')
      }
      if (scrollY > prevOffset) {
        nav?.classList.remove('activee')
      } else if (scrollY < prevOffset) {
        nav?.classList.add('activee')
      }
      setPrevOffset(scrollY)
    }

    window.addEventListener("scroll", toggleScrollDirection)
    // 清理函数移除监听,避免内存泄漏
    return () => window.removeEventListener("scroll", toggleScrollDirection)
  }, [prevOffset]) // 加入依赖项,解决闭包问题

  return scrollDirection
}

// 组件内部代码
const YourComponent = () => {
  const { width } = useWindowSize()
  const [mobile, setMobile] = React.useState(false)
  // 在组件顶层调用自定义Hook
  const scrollDirection = useScrollDirection()

  React.useEffect(() => {
    setMobile(width < 600)
  }, [width])

  // 组件其他逻辑
  return <div>/* 组件内容 */</div>
}

2. 修复闭包陷阱

原代码中toggleScrollDirection依赖prevOffset,但Effect没有将其加入依赖数组,会导致函数始终捕获初始的prevOffset值。修复时必须把prevOffset添加到Effect的依赖列表中,保证函数能获取到最新的状态。

3. 优化DOM操作(可选)

如果用React开发,更推荐用状态控制类名,而非直接操作DOM,这样更符合React的数据流逻辑:

const useScrollDirection = () => {
  const [isNavActive, setIsNavActive] = React.useState(false)
  const [prevOffset, setPrevOffset] = React.useState(0)

  React.useEffect(() => {
    if (typeof window === 'undefined') return

    const toggleScrollDirection = () => {
      const scrollY = window.scrollY
      if (scrollY === 0) {
        setIsNavActive(false)
      } else {
        setIsNavActive(scrollY < prevOffset)
      }
      setPrevOffset(scrollY)
    }

    window.addEventListener("scroll", toggleScrollDirection)
    return () => window.removeEventListener("scroll", toggleScrollDirection)
  }, [prevOffset])

  return isNavActive
}

// 组件内使用状态控制类名
const YourComponent = () => {
  const isNavActive = useScrollDirection()

  return (
    <nav className={`nav ${isNavActive ? 'activee' : ''}`}>
      {/* 导航栏内容 */}
    </nav>
  )
}

内容的提问来源于stack exchange,提问作者jacob

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 17:57:24