React中MUI上下文菜单始终关联列表最后一项的问题
React Material UI上下文菜单点击始终触发最后一项事件的问题
问题重现
右键点击任意列表项打开上下文菜单,选择“编辑”后,弹窗始终显示最后一项(item3)的编辑提示;拖拽重排卡片后,触发的也是重排后最后一项的事件。已为列表项的div设置key属性,问题仍存在。
相关代码
export default function App() { const [contextMenu, setContextMenu] = React.useState(null); const myArray = ["item1", "item2", "item3"]; const handleContextMenu = (event) => { event.preventDefault(); setContextMenu( contextMenu === null ? { mouseX: event.clientX + 2, mouseY: event.clientY - 6 } : null ); }; const editItem = (i) => { setContextMenu(null); alert(`Editing ${i}`); }; return ( <div> {myArray.map((i) => ( <div key={i} onContextMenu={handleContextMenu}> <Card sx={{ backgroundColor: "gray", m: 2 }}> <CardContent> <Typography>{i}</Typography> </CardContent> </Card> <Menu open={contextMenu != null} onClose={() => setContextMenu(null)} anchorReference="anchorPosition" anchorPosition={ contextMenu != null ? { top: contextMenu.mouseY, left: contextMenu.mouseX } : undefined } > <MenuItem onClick={() => editItem(i)}>Edit</MenuItem> </Menu> </div> ))} </div> ); }
问题原因
这是JavaScript闭包陷阱导致的:
在map循环中创建的onClick={() => editItem(i)}箭头函数,捕获的是循环变量i的引用,而非每次循环时i的具体值。当循环执行完毕,i最终指向数组的最后一个元素item3,因此无论点击哪个菜单,执行时都会使用这个最终的i值。
设置key属性只是帮助React识别列表项的唯一性、优化渲染性能,它解决不了JavaScript函数作用域层面的闭包问题。
解决方案
方案1:抽离独立组件(最推荐)
将每个带上下文菜单的卡片抽成独立组件,每个组件拥有自己的作用域,从根源上避免闭包变量共享问题:
function ItemCard({ item }) { const [contextMenu, setContextMenu] = React.useState(null); const handleContextMenu = (event) => { event.preventDefault(); setContextMenu(contextMenu === null ? { mouseX: event.clientX + 2, mouseY: event.clientY - 6 } : null ); }; const editItem = () => { setContextMenu(null); alert(`Editing ${item}`); }; return ( <div onContextMenu={handleContextMenu}> <Card sx={{ backgroundColor: "gray", m: 2 }}> <CardContent> <Typography>{item}</Typography> </CardContent> </Card> <Menu open={contextMenu != null} onClose={() => setContextMenu(null)} anchorReference="anchorPosition" anchorPosition={contextMenu != null ? { top: contextMenu.mouseY, left: contextMenu.mouseX } : undefined } > <MenuItem onClick={editItem}>Edit</MenuItem> </Menu> </div> ); } export default function App() { const myArray = ["item1", "item2", "item3"]; return ( <div> {myArray.map((item) => ( <ItemCard key={item} item={item} /> ))} </div> ); }
方案2:使用立即执行函数捕获当前值
在循环中通过立即执行函数创建闭包,主动捕获每次循环的i值:
<MenuItem onClick={(function(currentItem) { return () => editItem(currentItem); })(i)}>Edit</MenuItem>
方案3:用useCallback绑定独立处理函数
在循环内部为每个项创建独立的回调函数,通过依赖项[i]确保作用域正确:
{myArray.map((i) => { const handleEdit = React.useCallback(() => { editItem(i); }, [i]); return ( <div key={i} onContextMenu={handleContextMenu}> <Card sx={{ backgroundColor: "gray", m: 2 }}> <CardContent> <Typography>{i}</Typography> </CardContent> </Card> <Menu open={contextMenu != null} onClose={() => setContextMenu(null)} anchorReference="anchorPosition" anchorPosition={ contextMenu != null ? { top: contextMenu.mouseY, left: contextMenu.mouseX } : undefined } > <MenuItem onClick={handleEdit}>Edit</MenuItem> </Menu> </div> ); })}
内容的提问来源于stack exchange,提问作者derekantrican
相关产品推荐
相关产品推荐

