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

Next.js状态更新后条件类绑定不生效的问题排查

问题解决思路与代码修正

核心问题

你当前的问题是把渲染好的JSX元素存在了menuItems状态中,导致selectedItem更新时,菜单无法重新渲染——因为初始渲染的JSX是基于null的selectedItem生成的,后续状态变化不会触发重新调用renderMenuItems。

修正步骤

  1. 存储原始菜单数据而非JSX:把menuItems状态改为存储从API获取的原始MenuItem[]数据,而非渲染后的元素。
  2. 在组件渲染阶段动态生成菜单:直接在return里调用renderMenuItems,传入当前的selectedItem状态,确保状态变化时菜单能重新渲染。
  3. 实现隐藏其他菜单项与显示子菜单:通过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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 01:30:33