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

React+Material UI:动态标签页与菜单集成问题排查

动态标签页+菜单功能问题排查与修复

问题描述

要实现的功能:第一个标签页带图标,点击弹出菜单,选择菜单项动态创建新标签页,支持标签切换。
实际遇到的问题:

  • 菜单偶尔无法打开
  • 新建标签的ID(标题)错误
  • 疑惑初始标签的tabIndex总是-1而非0

原代码

import { useState, useEffect, React } from "react";
import { Tab, Divider, Menu, MenuItem } from "@mui/material";
import TabContext from "@mui/lab/TabContext";
import TabList from "@mui/lab/TabList";
import TabPanel from "@mui/lab/TabPanel";
import AddIcon from "@mui/icons-material/Add";

export default function TabNavigator() {
  const [data, setData] = useState([
    {
      title: "Home",
    },
  ]);

  const createTabs = (tabArray) => {
    return tabArray.map((tab, index) => (
      <Tab
        value={`${index + 1}`}
        key={index + 1}
        tabIndex={index + 1}
        label={tab.title}
      />
    ));
  };

  const createTabPanels = (tabArray) => {
    return tabArray.map((tab, index) => (
      <TabPanel
        value={`${index + 1}`}
        key={index + 1}
        // tabIndex={index+1}
      >
        {tab.title}
      </TabPanel>
    ));
  };

  const [selectedTab, setSelectedTab] = useState("1");

  const handleChange = (event, newValue: string) => {
    console.log("handleChange");
    // console.log(event)
    // console.log('newValue')
    // console.log(newValue)
    if (event.target.tabIndex <= 0) {
      console.log("event.target.tabIndex < 0");
      // console.log(event.target.tabIndex)
      setAnchorMenuEl(event.currentTarget);
    } else {
      console.log("event.target.tabIndex >= 0");
      console.log("event.target.tabIndex");
      console.log(event.target.tabIndex);
      setSelectedTab(`${event.target.tabIndex}`);
    }
  };

  const [anchorMenuEl, setAnchorMenuEl] = useState(null);
  const open = Boolean(anchorMenuEl);

  const [created, setCreated] = useState(true);

  const handleAddTab = (event) => {
    console.log("handleAdd");
    // console.log(event)
    event.preventDefault();
    if (event.target.id === "basic-button") {
      console.log("event.target.id===basic-button");
      // console.log('basic-button')
      // console.log('event.target.id')
      // console.log(event.target.id)
      // console.log('anchorMenuEl')
      // console.log(anchorMenuEl)
      // console.log('event.currentTarget')
      // console.log(event.currentTarget)
      setAnchorMenuEl(event.currentTarget);
      return;
    } else {
      console.log("event.target.id!=basic-button");
      // console.log('not button')
      // console.log('event.target.id')
      // console.log(event.target.id)
      if (event.target.id) {
        console.log("event.target.id");
        setData((data) => [...data, { title: event.target.id }]);
        // console.log(data)
        setCreated(true);
      }
      setAnchorMenuEl(null);
    }
    // console.log(data)
  };

  useEffect(() => {
    console.log("In the useEffect");
    // console.log('created: '+created)
    // console.log('data:')
    // console.log(data)
    if (created) {
      console.log("created");
      setSelectedTab(`${data.length}`);
      setCreated(false);
    } else {
      return;
    }
  }, [data, created]);

  return (
    <TabContext value={selectedTab}>
      <TabList
        onChange={(e) => {
          console.log("TabList onChange");
          // console.log(e)
          if (e.target.tabIndex < 0) {
            handleAddTab(e);
          } else {
            handleChange(e);
          }
        }}
        aria-label=""
      >
        <Tab
          value="0"
          key="0"
          label={<AddIcon id="basic-button" />}
          sx={{
            minWidth: "10px",
            width: "10px",
          }}
          // id='basic-button'
          aria-controls={open ? "menu" : undefined}
          aria-haspopup="true"
          aria-expanded={open ? "true" : undefined}
        ></Tab>
        {createTabs(data)}
      </TabList>
      <Divider />
      <Menu
        id="menu"
        MenuListProps={{
          "aria-labelledby": "basic-button",
        }}
        sx={{
          top: "50px",
        }}
        anchorEl={anchorMenuEl}
        open={open}
        onClose={handleAddTab}
        anchorOrigin={{
          vertical: "top",
          horizontal: "left",
        }}
        transformOrigin={{
          vertical: "top",
          horizontal: "left",
        }}
      >
        <MenuItem id="item1" onClick={handleAddTab}>
          Item 1
        </MenuItem>
        <MenuItem id="item2" onClick={handleAddTab}>
          Item 2
        </MenuItem>
        <MenuItem id="item3" onClick={handleAddTab}>
          Item 3
        </MenuItem>
        <MenuItem id="item4" onClick={handleAddTab}>
          Item 4
        </MenuItem>
        <MenuItem id="item5" onClick={handleAddTab}>
          Item 5
        </MenuItem>
        <MenuItem id="item6" onClick={handleAddTab}>
          Item 6
        </MenuItem>
      </Menu>
      <TabPanel
        value="0"
        key="0"
        sx={{
          paddingTop: "1px",
        }}
      ></TabPanel>
      {createTabPanels(data)}
    </TabContext>
  );
}

问题排查与修复方案

1. 菜单偶尔无法打开的问题

原因:

  • 把id="basic-button"加在了内部的AddIcon上,点击时event.target可能指向Icon而非外层Tab,导致id判断失效
  • TabList的onChange逻辑依赖tabIndex判断操作,逻辑混淆且不可靠

修复:

  • 将id="basic-button"直接绑定到Add Tab组件上,而非内部图标
  • 给Add Tab单独绑定onClick事件触发菜单,和普通标签的切换逻辑彻底分开,不用通过TabList的onChange间接处理

2. 新建标签ID错误的问题

原因:
点击MenuItem时,event.target可能指向MenuItem内部的文本节点,而非MenuItem本身,导致获取到的id为空或错误

修复:
使用event.currentTarget.id获取MenuItem的id,currentTarget始终指向绑定事件的那个元素,不会受内部节点影响

3. 初始标签tabIndex为-1的问题

原因:
手动设置tabIndex是错误的,MUI的Tab组件会自动管理tabIndex:选中的标签tabIndex为0,未选中的为-1,这是符合无障碍设计的默认行为,不需要手动干预

修复:
移除所有手动设置的tabIndex属性,完全依赖MUI的自动管理

修复后的完整代码

import { useState, React } from "react";
import { Tab, Divider, Menu, MenuItem } from "@mui/material";
import TabContext from "@mui/lab/TabContext";
import TabList from "@mui/lab/TabList";
import TabPanel from "@mui/lab/TabPanel";
import AddIcon from "@mui/icons-material/Add";

export default function TabNavigator() {
  const [data, setData] = useState([
    {
      title: "Home",
    },
  ]);

  const createTabs = (tabArray) => {
    return tabArray.map((tab, index) => (
      <Tab
        value={`${index + 1}`}
        key={index + 1}
        label={tab.title}
      />
    ));
  };

  const createTabPanels = (tabArray) => {
    return tabArray.map((tab, index) => (
      <TabPanel
        value={`${index + 1}`}
        key={index + 1}
      >
        {tab.title}
      </TabPanel>
    ));
  };

  const [selectedTab, setSelectedTab] = useState("1");

  // 处理标签切换
  const handleTabChange = (event, newValue) => {
    setSelectedTab(newValue);
  };

  const [anchorMenuEl, setAnchorMenuEl] = useState(null);
  const open = Boolean(anchorMenuEl);

  // 打开菜单
  const handleMenuOpen = (event) => {
    setAnchorMenuEl(event.currentTarget);
  };

  // 关闭菜单
  const handleMenuClose = () => {
    setAnchorMenuEl(null);
  };

  // 添加新标签
  const handleAddTab = (event) => {
    const tabTitle = event.currentTarget.id;
    setData(prev => [...prev, { title: tabTitle }]);
    // 切换到新创建的标签
    setSelectedTab(`${data.length + 1}`);
    handleMenuClose();
  };

  return (
    <TabContext value={selectedTab}>
      <TabList
        onChange={handleTabChange}
        aria-label="动态标签页"
      >
        <Tab
          value="0"
          key="0"
          label={<AddIcon />}
          sx={{ minWidth: "40px" }}
          id="basic-button"
          onClick={handleMenuOpen}
          aria-controls={open ? "menu" : undefined}
          aria-haspopup="true"
          aria-expanded={open ? "true" : undefined}
        />
        {createTabs(data)}
      </TabList>
      <Divider />
      <Menu
        id="menu"
        MenuListProps={{ "aria-labelledby": "basic-button" }}
        anchorEl={anchorMenuEl}
        open={open}
        onClose={handleMenuClose}
        anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
        transformOrigin={{ vertical: "top", horizontal: "left" }}
      >
        <MenuItem id="item1" onClick={handleAddTab}>Item 1</MenuItem>
        <MenuItem id="item2" onClick={handleAddTab}>Item 2</MenuItem>
        <MenuItem id="item3" onClick={handleAddTab}>Item 3</MenuItem>
        <MenuItem id="item4" onClick={handleAddTab}>Item 4</MenuItem>
        <MenuItem id="item5" onClick={handleAddTab}>Item 5</MenuItem>
        <MenuItem id="item6" onClick={handleAddTab}>Item 6</MenuItem>
      </Menu>
      {createTabPanels(data)}
    </TabContext>
  );
}

内容的提问来源于stack exchange,提问作者alex

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 12:01:08