ReactJS中Header导航切换按钮状态与类不更新问题排查
React中点击导航切换按钮后状态与类未更新问题
我开发了一个包含Header组件的React应用,Header里的SVG切换按钮点击后本该修改isNavOpen状态,添加/移除类来显示菜单,但点击后状态和类都没变化。不过通过React DevTools直接修改状态时,菜单能正常显示。
代码示例
App.jsx
import { BrowserRouter as Router, Route, Routes, Navigate } from 'react-router-dom' import Leaderboard from './pages/Leaderboard' import About from './pages/About' import Scoresfeed from './pages/Scores-feed' import Header from './components/Header/Header' import './App.css' const App = () => { return ( <Router> <Header/> <Routes> <Route path="/about" element={<About />} /> <Route path="/scores-feed" element={<Scoresfeed />} /> <Route path="/" element={<Navigate to="/leaderboard" />} /> <Route path="/leaderboard" element={<Leaderboard />} /> </Routes> </Router> ); }; export default App;
Header.jsx
import { useState, useEffect, useRef } from 'react'; import { Link } from 'react-router-dom'; import './Header.css'; const Header = () => { const [isNavOpen, setIsNavOpen] = useState(false); const navRef = useRef(null); useEffect(() => { const handleOutsideClick = (event) => { if (navRef.current && !navRef.current.contains(event.target)) { setIsNavOpen(false); } }; document.addEventListener('click', handleOutsideClick); return () => { document.removeEventListener('click', handleOutsideClick); }; }, [isNavOpen]); const handleNavToggle = () => { setIsNavOpen((isNavOpen) => !isNavOpen); }; return ( <header className="header"> <div className="nav-toggle" onClick={handleNavToggle}> <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={`feather feather-chevron-down ${isNavOpen ? 'open' : ''}`} > <polyline points="6 9 12 15 18 9"></polyline> </svg> <span className={`nav-toggle-label ${isNavOpen ? 'hidden' : ''}`}>Open navigation</span> </div> <nav ref={navRef} className={`nav ${isNavOpen ? 'open' : ''}`}> <ul className="nav-list"> <li className="nav-item"> <Link to="/leaderboard"> <svg width="140" height="50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 50" preserveAspectRatio="xMidYMid meet" className="svg-icon" > <rect x="5" y="40" width="5" height="5" fill="none" stroke="rgb(19, 65, 150)" strokeWidth="2" /> <rect x="180" y="5" width="5" height="5" fill="none" stroke="rgb(19, 65, 150)" strokeWidth="2" /> <line x1="1" y1="12" x2="12" y2="1" stroke="rgb(19, 65, 150)" strokeWidth="2" /> <line x1="179" y1="49" x2="190" y2="37" stroke="rgb(19, 65, 150)" strokeWidth="2" /> <polygon points="20,0 190,0 190,30 172,50 0,50 0,40 0,20" fill="none" stroke="rgb(19, 65, 150)" strokeWidth="3" className="svg-border" /> <text className='text' x="50%" y="50%" textAnchor="middle" fill="white" dy=".3em" fontSize="30">Leaderboard</text> </svg> </Link> </li> <li className="nav-item"> <Link to="/scores-feed"> <svg width="140" height="50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 50" preserveAspectRatio="xMidYMid meet" className="svg-icon" > <rect x="5" y="40" width="5" height="5" fill="none" stroke="rgb(19, 65, 150)" strokeWidth="2" /> <rect x="180" y="5" width="5" height="5" fill="none" stroke="rgb(19, 65, 150)" strokeWidth="2" /> <line x1="1" y1="12" x2="12" y2="1" stroke="rgb(19, 65, 150)" strokeWidth="2" /> <line x1="179" y1="49" x2="190" y2="37" stroke="rgb(19, 65, 150)" strokeWidth="2" /> <polygon points="20,0 190,0 190,30 172,50 0,50 0,40 0,20" fill="none" stroke="rgb(19, 65, 150)" strokeWidth="3" className="svg-border" /> <text className='text' x="50%" y="50%" textAnchor="middle" fill="white" dy=".3em" fontSize="30">Scores feed</text> </svg> </Link> </li> <li className="nav-item"> <Link to="/about"> <svg width="140" height="50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 50" preserveAspectRatio="xMidYMid meet" className="svg-icon" > <rect x="5" y="40" width="5" height="5" fill="none" stroke="rgb(19, 65, 150)" strokeWidth="2" /> <rect x="180" y="5" width="5" height="5" fill="none" stroke="rgb(19, 65, 150)" strokeWidth="2" /> <line x1="1" y1="12" x2="12" y2="1" stroke="rgb(19, 65, 150)" strokeWidth="2" /> <line x1="179" y1="49" x2="190" y2="37" stroke="rgb(19, 65, 150)" strokeWidth="2" /> <polygon points="20,0 190,0 190,30 172,50 0,50 0,40 0,20" fill="none" stroke="rgb(19, 65, 150)" strokeWidth="3" className="svg-border" /> <text className='text' x="50%" y="50%" textAnchor="middle" fill="white" dy=".3em" fontSize="30">About</text> </svg> </Link> </li> </ul> </nav> </header> ); }; export default Header;
问题原因
点击切换按钮时,handleNavToggle会切换isNavOpen状态,但事件会冒泡到文档,触发handleOutsideClick——因为切换按钮不在navRef指向的导航菜单内部,这个函数会立刻把isNavOpen改回false,导致状态看起来没有变化。同时useEffect依赖了isNavOpen,每次状态变化都会重新绑定事件,加剧了这个冲突。
修复方案
1. 阻止切换按钮的事件冒泡
修改handleNavToggle,添加事件阻止冒泡,避免触发文档级的点击监听:
const handleNavToggle = (e) => { e.stopPropagation(); setIsNavOpen((prev) => !prev); };
2. 优化useEffect的依赖项
handleOutsideClick逻辑不依赖isNavOpen,可以移除该依赖,避免不必要的事件重新绑定:
useEffect(() => { const handleOutsideClick = (event) => { if (navRef.current && !navRef.current.contains(event.target)) { setIsNavOpen(false); } }; document.addEventListener('click', handleOutsideClick); return () => { document.removeEventListener('click', handleOutsideClick); }; }, []); // 移除isNavOpen依赖
内容的提问来源于stack exchange,提问作者Chelovek 01
相关产品推荐
相关产品推荐

