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

Material UI 5 Context Menu问题:handleClose为何总是打印最后索引4?

问题:Material UI 5右键菜单点击后总是打印数组最后索引4的原因

我目前正在学习Material UI 5,重点研究右键菜单(Context Menu)模块。请帮忙解释为何以下代码中的handleClose函数总是打印数组的最后索引4?

import * as React from "react"
import Menu from "@mui/material/Menu"
import MenuItem from "@mui/material/MenuItem"
import Typography from "@mui/material/Typography"

export default function ContextMenu() {
  const [contextMenu, setContextMenu] = React.useState(null)

  const handleContextMenu = (event) => {
    event.preventDefault()
    setContextMenu(
      contextMenu === null
        ? {
            mouseX: event.clientX + 2,
            mouseY: event.clientY - 6,
          }
        : null
    )
  }

  const handleClose = (i) => {
    setContextMenu(null)
    console.log(i)
  }

  return (
    <>
      {["a", "b", "c", "d", "e"].map((item, index) => (
        <div
          key={index}
          onContextMenu={handleContextMenu}
          style={{ cursor: "context-menu" }}
        >
          <Typography variant='h6' component='h1' sx={{ padding: 4 }}>
            {item}
          </Typography>
          <Menu
            open={contextMenu !== null}
            onClose={handleClose}
            anchorReference='anchorPosition'
            anchorPosition={
              contextMenu !== null
                ? { top: contextMenu.mouseY, left: contextMenu.mouseX }
                : undefined
            }
          >
            <MenuItem onClick={() => handleClose(index)}>one</MenuItem>
            <MenuItem onClick={() => handleClose(index)}>two</MenuItem>
            <MenuItem onClick={() => handleClose(index)}>three</MenuItem>
          </Menu>
        </div>
      ))}
    </>
  )
}

原因分析

1. 闭包陷阱导致索引值未被正确捕获

在map循环中创建的onClick回调() => handleClose(index),并没有保存当前迭代的index值,而是持有对index变量的引用。循环执行时,index会不断更新,直到循环结束后最终变为4(数组最后一项的索引)。当你点击菜单项时,回调才会执行,此时读取的是index的最终值,所以总是打印4。

2. 冗余的菜单组件加剧逻辑混乱

你给每个循环项都渲染了一个独立的Menu组件,所有菜单共享同一个contextMenu状态。右键点击任意元素时,所有菜单的open状态都会变为true,但实际只需要一个全局菜单来对应当前激活的元素,这种结构不仅浪费性能,还会让闭包问题的影响更明显。


修复方案

方案1:解决闭包问题(快速修复)

通过给MenuItem添加data-index属性,点击时从事件对象中获取当前索引,避免闭包引用问题:

<MenuItem 
  onClick={(e) => handleClose(Number(e.currentTarget.dataset.index))} 
  data-index={index}
>one</MenuItem>

方案2:优化菜单结构(推荐)

将Menu组件移到循环外部,只渲染一个菜单,同时在右键点击时记录当前元素的索引,确保菜单与激活元素一一对应:

import * as React from "react"
import Menu from "@mui/material/Menu"
import MenuItem from "@mui/material/MenuItem"
import Typography from "@mui/material/Typography"

export default function ContextMenu() {
  const [contextMenu, setContextMenu] = React.useState(null)
  // 新增状态保存当前点击的元素索引
  const [currentIndex, setCurrentIndex] = React.useState(null)

  const handleContextMenu = (event, index) => {
    event.preventDefault()
    setCurrentIndex(index)
    setContextMenu({
      mouseX: event.clientX + 2,
      mouseY: event.clientY - 6,
    })
  }

  const handleClose = () => {
    setContextMenu(null)
    // 直接使用保存的currentIndex
    console.log(currentIndex)
    setCurrentIndex(null)
  }

  return (
    <>
      {["a", "b", "c", "d", "e"].map((item, index) => (
        <div
          key={index}
          // 传递当前索引到右键事件
          onContextMenu={(e) => handleContextMenu(e, index)}
          style={{ cursor: "context-menu" }}
        >
          <Typography variant='h6' component='h1' sx={{ padding: 4 }}>
            {item}
          </Typography>
        </div>
      ))}
      {/* 只渲染一个全局菜单 */}
      <Menu
        open={contextMenu !== null}
        onClose={handleClose}
        anchorReference='anchorPosition'
        anchorPosition={
          contextMenu !== null
            ? { top: contextMenu.mouseY, left: contextMenu.mouseX }
            : undefined
        }
      >
        <MenuItem onClick={handleClose}>one</MenuItem>
        <MenuItem onClick={handleClose}>two</MenuItem>
        <MenuItem onClick={handleClose}>three</MenuItem>
      </Menu>
    </>
  )
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 05:06:27