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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 18:17:02