Next.js状态更新后条件类绑定不生效的问题排查
问题解决思路与代码修正
核心问题
你当前的问题是把渲染好的JSX元素存在了menuItems状态中,导致selectedItem更新时,菜单无法重新渲染——因为初始渲染的JSX是基于null的selectedItem生成的,后续状态变化不会触发重新调用renderMenuItems。
修正步骤
- 存储原始菜单数据而非JSX:把
menuItems状态改为存储从API获取的原始MenuItem[]数据,而非渲染后的元素。 - 在组件渲染阶段动态生成菜单:直接在
return里调用renderMenuItems,传入当前的selectedItem状态,确保状态变化时菜单能重新渲染。 - 实现隐藏其他菜单项与显示子菜单:通过
selectedItem判断,控制非选中项的隐藏逻辑,同时渲染选中项的子菜单。
修改后的完整代码
'use client'; import { css, cx } from '@/styled-system/css'; import Image from 'next/image'; import MainMenuActions from './mainMenuActions'; import MainMenuSearch from './mainMenuSearch'; import { useState, useEffect } from 'react'; import Hamburger from 'hamburger-react'; import IconSearchRegular from '@/components/icon/iconSearchRegular'; import IconChevronDownLight from '@/components/icon/iconChevronDownLight'; import { token } from '@/styled-system/tokens'; interface Props extends React.ComponentProps<'div'> { className?: string; topMenuIsOut: boolean; } interface MenuItem { title: string; href: string; newPage: boolean; childrens: MenuItem[]; // 修正类型:子项统一为数组,避免单个对象的类型歧义 } export default function MainMenuBottom(props: Props) { const { className, topMenuIsOut } = props; const [searchIsOpen, setSearchIsOpen] = useState(false); const [hamburgerIsOpen, setHamburgerIsOpen] = useState(false); const [menuData, setMenuData] = useState<MenuItem[]>([]); // 存储原始菜单数据 const [selectedItem, setSelectedItem] = useState<MenuItem | null>(null); const handleMenuItemClick = (clickedItem: MenuItem) => { // 点击已选中的项时取消选中,实现切换效果 setSelectedItem(selectedItem === clickedItem ? null : clickedItem); }; const renderMenuItems = (items: MenuItem[], selectedItem: MenuItem | null) => { return items.map((item) => { const isSelected = selectedItem === item; return ( <li onClick={() => handleMenuItemClick(item)} className={`d-flex align-items-center ${isSelected ? 'selected' : ''}`} key={item.href} // 有选中项时隐藏非选中项,无选中项时全部显示 style={{ display: selectedItem && !isSelected ? 'none' : 'flex' }} > {item.title} {item.childrens.length > 0 && ( <IconChevronDownLight size={8} color={token('colors.myYellow')} className={styles.bottomNavIcon} /> )} {/* 渲染选中项的子菜单 */} {isSelected && item.childrens.length > 0 && ( <ul className="submenu"> {renderMenuItems(item.childrens, null)} </ul> )} </li> ); }); }; useEffect(() => { fetch('https://www.myapiurl.com') .then((response) => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }) .then((data: { 'main-menu': MenuItem[] }) => { setMenuData(data['main-menu']); // 存储原始数据,而非渲染后的JSX }) .catch((error) => { console.error('Error fetching data:', error); }); }, []); return ( <div className={cx(styles.mainMenuBottom, className, 'container')}> {searchIsOpen && ( <div className={styles.bottomSearch}> <MainMenuSearch width="100%" placeholder="Kezdj el gépelni..." onClose={() => setSearchIsOpen(!searchIsOpen)} /> </div> )} <div className={cx(styles.bottomNavWrapper, !topMenuIsOut ? styles.bottomNavWrapperFloating : undefined)}> <div className={cx(styles.bottomNavLogo, !topMenuIsOut && !searchIsOpen ? styles.bottomNavLogoFloating : undefined)}> </div> <ul className={cx(styles.bottomNav, !topMenuIsOut ? styles.bottomNavFloating : undefined)}> {menuData.length > 0 && renderMenuItems(menuData, selectedItem)} {/* 动态渲染菜单,确保状态更新时重新生成元素 */} </ul> </div> <div className={styles.bottomIcons}> <MainMenuActions /> <div className={styles.bottomIconSearch} onClick={() => setSearchIsOpen(!searchIsOpen)}> <IconSearchRegular size={16} /> </div> <div className={styles.bottomIconHamburger}> <Hamburger size={26} toggled={hamburgerIsOpen} toggle={setHamburgerIsOpen} /> </div> </div> </div> ); }
关键修正点说明
- 状态存储调整:将
menuItems改为menuData,存储API返回的原始数组,确保每次状态变化时能重新生成菜单元素。 - 动态渲染菜单:在组件的
return中直接调用renderMenuItems,传入最新的selectedItem,这样状态更新时会自动重新渲染菜单,条件类selected就能正常生效。 - 隐藏非选中项:通过内联样式
style={{ display: selectedItem && !isSelected ? 'none' : 'flex' }}实现,当有选中项时,其他项隐藏;无选中项时全部显示。 - 子菜单渲染:在选中项的
li内渲染子菜单,递归调用renderMenuItems处理子项。 - 类型修正:将
MenuItem的childrens类型改为MenuItem[],避免单个对象的类型歧义,更符合菜单结构逻辑。
内容的提问来源于stack exchange,提问作者High T
相关产品推荐
相关产品推荐

