React Hook React.useEffect条件调用报错的原因及修复方案
React Hook 条件调用报错原因及修复方案
报错原因
你遇到的react-hooks/rules-of-hooks报错,核心是违反了React Hooks的强制调用规则:
- Hooks必须在组件的顶层作用域调用,不能嵌套在条件判断、循环、内部函数中
- 每次组件渲染时,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
相关产品推荐
相关产品推荐

