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

Next.js侧边面板点击菜单项无法关闭问题求助(附代码)

问题根源

每个组件调用自定义Hook时,都会生成独立的状态副本。SlideScreen和Nav各自调用useSlideScreen,导致两者的slideScreenActive状态完全隔离——Nav里修改的状态不会同步到SlideScreen中,自然无法触发面板关闭。另外原Hook中的useEffect未添加依赖数组,会引发无限重渲染,也需要一并修复。

解决方案一:状态提升 + Props传递

将状态逻辑放在父组件SlideScreen中,通过props把状态修改方法传递给Nav,让Nav直接调用父组件的方法来控制面板状态。

1. 修复自定义Hook的useEffect依赖

// hooks/useSlideScreen
import { useState, useEffect } from "react";

export default function useSlideScreen() {
  // 用布尔值更贴合开关场景,原0/非0逻辑也可保留
  const [slideScreenActive, setSlideScreenActive] = useState(false);

  useEffect(() => {
    document.body.className = slideScreenActive ? 'isSlideActive' : '';
  }, [slideScreenActive]); // 添加依赖数组,仅状态变化时执行

  return { slideScreenActive, setSlideScreenActive };
}

2. 修改SlideScreen组件,传递关闭方法给Nav

// components/slideScreen
import styles from "@/styles/slideScreen.module.css"
import Nav from "./nav";
import useSlideScreen from '@/hooks/useSlideScreen'

const SlideScreen = () => {
    const { slideScreenActive, setSlideScreenActive } = useSlideScreen()

    const menuBtnClasses = [styles.menuBtn, "mainBtn", slideScreenActive ? `${styles.active}` : null].join(" ")
    const slideScreenClasses = [slideScreenActive ? `${styles.slideScreen} ${styles.show}` : `${styles.slideScreen}`, "dotsBg"].join(" ")

    return (
        <>
            <button
                className={menuBtnClasses}
                onClick={() => setSlideScreenActive(!slideScreenActive)}
            >
                <span className={styles.burger}></span>
            </button>
            <div className={slideScreenClasses}>
                <div className="wrapper">
                    {/* 把关闭面板的方法通过props传给Nav */}
                    <Nav onClosePanel={() => setSlideScreenActive(false)} />
                </div>
            </div>
        </>
    )
}

export default SlideScreen;

3. 修改Nav组件,接收并调用关闭方法

// components/nav
import Link from "next/link";
import { useRouter } from "next/router";
import styles from "@/styles/header.module.css"

const Nav = ({ onClosePanel }) => {
    const router = useRouter();
    const routes = ['Blog', 'Work', 'Contact'];

    return (
        <nav className={styles.headerNav}>
            <ul className={styles.headerMenu}>
                {routes.map((route) => {
                    return (
                        <li key={route} className={`${router.pathname === `/${route.toLowerCase()}` && 'text-red-400'}`}>
                            <Link 
                                href={`/${route.toLowerCase()}`} 
                                onClick={onClosePanel} // 点击菜单项时触发关闭
                            >
                                {route}
                            </Link>
                        </li>
                    )
                })}
            </ul>
        </nav>
    )
}

export default Nav
解决方案二:使用React Context共享状态

如果后续组件层级较深,props传递过于繁琐,可通过Context全局共享面板状态和操作方法。

1. 创建SlideScreen Context

// contexts/SlideScreenContext.js
import { createContext, useContext, useState, useEffect } from 'react';

const SlideScreenContext = createContext();

export function SlideScreenProvider({ children }) {
  const [slideScreenActive, setSlideScreenActive] = useState(false);

  useEffect(() => {
    document.body.className = slideScreenActive ? 'isSlideActive' : '';
  }, [slideScreenActive]);

  // 封装常用操作方法
  const closeSlideScreen = () => setSlideScreenActive(false);
  const toggleSlideScreen = () => setSlideScreenActive(prev => !prev);

  return (
    <SlideScreenContext.Provider value={{ slideScreenActive, toggleSlideScreen, closeSlideScreen }}>
      {children}
    </SlideScreenContext.Provider>
  );
}

// 自定义Hook简化Context调用
export function useSlideScreen() {
  return useContext(SlideScreenContext);
}

2. 在_app.js中全局包裹Provider

// pages/_app.js
import { SlideScreenProvider } from '../contexts/SlideScreenContext';
import '../styles/globals.css';

function MyApp({ Component, pageProps }) {
  return (
    <SlideScreenProvider>
      <Component {...pageProps} />
    </SlideScreenProvider>
  );
}

export default MyApp;

3. 修改SlideScreen组件使用Context

// components/slideScreen
import styles from "@/styles/slideScreen.module.css"
import Nav from "./nav";
import { useSlideScreen } from '@/contexts/SlideScreenContext'

const SlideScreen = () => {
    const { slideScreenActive, toggleSlideScreen } = useSlideScreen()

    const menuBtnClasses = [styles.menuBtn, "mainBtn", slideScreenActive ? `${styles.active}` : null].join(" ")
    const slideScreenClasses = [slideScreenActive ? `${styles.slideScreen} ${styles.show}` : `${styles.slideScreen}`, "dotsBg"].join(" ")

    return (
        <>
            <button
                className={menuBtnClasses}
                onClick={toggleSlideScreen}
            >
                <span className={styles.burger}></span>
            </button>
            <div className={slideScreenClasses}>
                <div className="wrapper">
                    <Nav />
                </div>
            </div>
        </>
    )
}

export default SlideScreen;

4. 修改Nav组件使用Context

// components/nav
import Link from "next/link";
import { useRouter } from "next/router";
import { useSlideScreen } from '@/contexts/SlideScreenContext'
import styles from "@/styles/header.module.css"

const Nav = () => {
    const router = useRouter();
    const routes = ['Blog', 'Work', 'Contact'];
    const { closeSlideScreen } = useSlideScreen();

    return (
        <nav className={styles.headerNav}>
            <ul className={styles.headerMenu}>
                {routes.map((route) => {
                    return (
                        <li key={route} className={`${router.pathname === `/${route.toLowerCase()}` && 'text-red-400'}`}>
                            <Link 
                                href={`/${route.toLowerCase()}`} 
                                onClick={closeSlideScreen}
                            >
                                {route}
                            </Link>
                        </li>
                    )
                })}
            </ul>
        </nav>
    )
}

export default Nav

内容的提问来源于stack exchange,提问作者Леонид Колтан

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 02:47:09