深入理解React事件冒泡/传播与状态管理:汉堡菜单点击异常排查
问题原因
你的猜测完全正确,这个现象的核心是 React合成事件和原生DOM事件的触发优先级差异:
- React的所有合成事件(就是你写在JSX里的
onClick这类事件)默认不会绑定在对应的DOM节点上,而是统一托管在顶层根节点(React 16及更早是document,React 17+是你调用ReactDOM.render绑定的根元素),等原生事件冒泡到顶层节点后,React才会模拟冒泡流程触发合成事件。 - 你给
#App节点用原生addEventListener绑定的点击事件是直接挂在DOM节点上的,事件冒泡过程中会先触发这个原生事件,之后才会触发React的合成事件,和你预期的子元素先触发、父元素后触发的顺序不一致。
具体到你的场景触发流程是:
- 点击汉堡图标要关闭菜单时,事件先向上冒泡到
#App节点,触发你绑定的原生handleMenuClick函数 - 此时逻辑判断点击目标不在ul列表内,直接把
showMenu设为false,触发组件重渲染 - 事件继续冒泡到顶层根节点,才触发汉堡图标绑定的合成
onClick事件,此时闭包拿到的showMenu还是之前的true,所以执行setShowMenu(!showMenu)又把状态改成了true,看起来就像点击汉堡没反应。
更稳妥的修复方案
你当前加event.stopPropagation()的方法可以解决问题,但会阻断#App节点上所有点击事件的冒泡,可能影响其他业务逻辑,更合理的方案是把汉堡图标也加入点击排除范围:
- 给
MenuIcon组件添加ref,注意MenuIcon是函数组件,需要用React.forwardRef包裹转发ref - 修改全局点击的判断逻辑,点击目标如果是汉堡图标本身,就不执行关闭菜单的逻辑
优化后的MobileNav核心代码示例:
export default function MobileNav() { const [showMenu, setShowMenu] = useState(false); const ulRef = useRef(); // 新增汉堡图标ref const menuIconRef = useRef(); useEffect(() => { const handleMenuClick = (event) => { // 点击目标不在ul内,同时也不在汉堡图标内时,才关闭菜单 if(ulRef.current && !ulRef.current.contains(event.target) && !menuIconRef.current.contains(event.target) ){ setShowMenu(false); } } document.querySelector('#App').addEventListener('click', handleMenuClick); return () => document.querySelector('#App').removeEventListener('click', handleMenuClick); }, []) return ( <StyledMobileNav> <PersonOutlineIcon /> {/* 给MenuIcon传ref */} <MenuIcon ref={menuIconRef} showMenu={showMenu} setShowMenu={setShowMenu} /> {showMenu && <ul ref={ulRef}>{/* 菜单内容 */}</ul>} </StyledMobileNav> ) }
MenuIcon需要用forwardRef包裹:
const MenuIcon = React.forwardRef(({showMenu, setShowMenu}, ref) => { const handleMenuClick = (event) => { setShowMenu(!showMenu); } return ( <MenuWrap ref={ref} onClick={handleMenuClick} showMenu={showMenu}> <TopLine showMenu={showMenu}></TopLine> <MidLine showMenu={showMenu}></MidLine> <BottomLine showMenu={showMenu}></BottomLine> </MenuWrap> ) }) export default MenuIcon;
内容的提问来源于stack exchange,提问作者user16422658
相关产品推荐
相关产品推荐

