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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 08:43:15