React中使用useState切换overflow样式实现滚动条锁定解锁
问题根源
你当前代码的逻辑缺陷非常明确:toggleClass 函数中没有对菜单状态做分支判断,无论点击后是展开还是收起菜单,都会强制把 document.body.style.overflow 设为 hidden,所以关闭菜单时滚动锁定永远不会被解除。
另外不推荐在点击事件回调里直接混写DOM操作:React的状态更新是异步的,直接读取当前isActive值做判断很容易拿到旧值,导致状态和UI表现不一致。
正确实现方案
最稳妥的写法是把滚动锁定的逻辑作为isActive状态的副作用,用useEffect监听状态变化自动同步,保证DOM属性和组件状态100%匹配:
import { useState, useEffect } from 'react'; const [isActive, setActive] = useState(false); // 菜单状态变化时自动同步滚动锁定规则 useEffect(() => { // 展开时锁滚动,收起时恢复 document.body.style.overflow = isActive ? 'hidden' : 'unset'; // 组件卸载时兜底恢复滚动,避免组件销毁后页面被意外锁定 return () => { document.body.style.overflow = 'unset'; }; }, [isActive]); const toggleMenu = () => { // 点击事件只负责切换状态,DOM修改统一交给副作用处理 setActive(prevState => !prevState); }; return ( <div onClick={toggleMenu} className={isActive ? 'nav-button closebtn active' : 'nav-button closebtn'} > <span></span> </div> );
可选简化写法(不推荐)
如果你不想引入useEffect,也可以在setActive的状态更新回调里拿到最新状态做判断,但这种写法无法覆盖组件初始化、卸载时的状态兜底,容易出边界问题:
const toggleClass = () => { setActive(prev => { const nextActive = !prev; document.body.style.overflow = nextActive ? 'hidden' : 'unset'; return nextActive; }) };
补充说明:如果你的页面本身给body设置过自定义
overflow值,不要直接写死unset,可以在锁定前先存下原始的overflow值,解锁时恢复成原始值即可,避免覆盖原有页面样式。
内容的提问来源于stack exchange,提问作者Serge Yudin
相关产品推荐
相关产品推荐

