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
相关产品推荐
相关产品推荐

