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

React实现多页面Navbar差异化渲染及解决更新深度超限警告

解决React中Maximum update depth exceeded警告问题

问题描述

在React项目中实现首页显示完整导航项、其他页面仅显示Home导航项的功能时,功能正常但控制台出现以下警告:
react-dom.development.js:86 Warning: Maximum update depth exceeded.

问题根源

出现该警告的核心原因是无限更新循环:

  • 监听导航列表的useEffect设置了依赖项[nav],而useEffect内部调用的navList()函数会通过setNav()修改nav状态。
  • 每次nav状态更新都会触发组件重新渲染,进而再次触发useEffect,形成无限循环,最终导致更新深度超限。

另外,组件顶层直接执行DOM查询和事件绑定的代码,会在每次组件渲染时重复执行,可能引发不必要的问题。

修复方案

1. 修正useEffect依赖并重构导航逻辑

将useEffect的依赖从nav改为path(导航列表仅需在路径变化时更新),甚至可以直接移除nav状态,在渲染阶段根据path直接生成导航项,简化逻辑。

2. 规范DOM操作与事件绑定

将DOM查询和事件绑定逻辑移入useEffect,并添加清理函数避免内存泄漏;主题切换时用状态驱动图片src,而非直接操作DOM。

修复后的完整代码

import React, { useEffect, useState } from 'react'
import { motion } from 'framer-motion'
import { Link, useLocation } from 'react-router-dom'

const ProjectHeader = () => {
    // 深色模式状态
    const [theme, setTheme] = useState("dark-mode");

    const lightdarkmode = () => {
        setTheme(prevTheme => prevTheme === "dark-mode" ? "light-mode" : "dark-mode");
    }

    useEffect(() => {
        document.body.className = theme;
        // 根据主题设置图片src,用状态驱动而非直接操作DOM
        const brandIcon = document.getElementById("brand_icon");
        const arrow = document.getElementById("arrow");
        const hamburgerMenu = document.getElementById("hamburger_menu");
        
        if (theme === "dark-mode") {
            brandIcon.src = "assets/icons/svg_light_logo.svg";
            arrow?.src = "assets/icons/light_arrow.png";
            hamburgerMenu.src = "assets/icons/icon_menu.png";
        } else {
            brandIcon.src = "assets/icons/svg_dark_logo.svg";
            arrow?.src = "assets/icons/dark_arrow.png";
            hamburgerMenu.src = "assets/icons/icon_menu_dark.png";
        }
    }, [theme])

    // 导航数据
    const default_nav_link = [
        { name: "Home", link: "#home-page", isLink: false },
        { name: "About Me", link: "#about-page", isLink: false },
        { name: "Skills", link: "#skill-page", isLink: false },
        { name: "Projects", link: "#project-page", isLink: false },
        { name: "Contact", link: "#contact-page", isLink: false }
    ]

    const project_nav_link = [
        { name: "Home", link: "/", isLink: true }
    ]

    const location = useLocation()
    const path = location.pathname

    // 导航项点击关闭折叠菜单的逻辑,移入useEffect
    useEffect(() => {
        const navBar = document.querySelectorAll(".nav-link");
        const navCollapse = document.getElementById("navbarNav");
        
        const handleNavClick = () => {
            navCollapse?.classList.remove("show");
        };

        navBar.forEach(a => a.addEventListener("click", handleNavClick));
        
        // 清理函数,移除事件监听防止内存泄漏
        return () => {
            navBar.forEach(a => a.removeEventListener("click", handleNavClick));
        };
    }, []);

    // 直接在渲染时生成导航项,无需额外状态
    const renderNavItems = () => {
        const navLinks = path === "/" ? default_nav_link : project_nav_link;
        return navLinks.map((list, index) => (
            <li key={index} className="nav-item">
                {list.isLink ? (
                    <Link to={list.link} className='nav-link'>{list.name}</Link>
                ) : (
                    <a href={list.link} className='nav-link'>{list.name}</a>
                )}
            </li>
        ));
    }

    return (
        <motion.section
            initial={{ y: -300, opacity: 0 }}
            animate={{ y: 0, opacity: 1 }}
            transition={{ delay: 0.3, duration: 1.3, type: 'tween', ease: "easeInOut" }}
            className="header-wrapper">
            <nav className="navbar navbar-expand-lg fixed-top">
                <div className="container">
                    {/* 网站Logo */}
                    <Link to="/" className="navbar-brand">
                        <img id='brand_icon' src="assets/icons/svg_light_logo.svg" alt="网站Logo" />
                    </Link>

                    {/* 汉堡菜单按钮 */}
                    <button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
                        aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
                        <img id='hamburger_menu' src="assets/icons/icon_menu.png" alt="菜单按钮" />
                    </button>
                     
                    {/* 导航菜单 */}
                    <div className="collapse navbar-collapse" id="navbarNav">
                        <ul className="navbar-nav ms-auto">
                            {/* 导航项 */}
                            {renderNavItems()}
                            <li className="nav-item">
                                <button className="nav-link" onClick={lightdarkmode}><i className="fa-solid fa-sun"></i></button>
                            </li>
                        </ul>
                    </div>
                </div>
            </nav>
        </motion.section>
    );
}

export default ProjectHeader;

额外优化说明

  • 移除了不必要的nav状态,直接在渲染阶段根据路径生成导航项,避免状态更新带来的循环问题。
  • 将主题切换的DOM操作移入useEffect,通过状态驱动图片资源,符合React的状态管理原则。
  • 导航点击事件的绑定添加了清理函数,防止组件卸载后残留事件监听导致内存泄漏。
  • 给导航项添加了key属性,符合React列表渲染的规范。

内容的提问来源于stack exchange,提问作者soumya prakash sahu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 04:18:30