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,提问作者Леонид Колтан
相关产品推荐
相关产品推荐

