即时创建 React 上下文菜单

Car*_*ten 3 javascript contextmenu reactjs react-redux

有没有办法即时创建 React 组件(或者更具体地说:上下文菜单),即仅当用户请求这样的上下文菜单时?

构建单个上下文菜单所需的所有信息都已经存在于触发它的组件上,这意味着无需等待任何异步获取数据。

在其他情况下,这可以很容易地实现,但我无法理解在 React+Redux 应用程序中应该如何做到这一点。


在编写本文时唯一想到的是在触发上下文菜单时发送一个动作,并将这个单个上下文菜单作为某种弹出对话框包含在结果重新渲染中。

这至少可以避免不可见组件的过早创建。但不知何故,它感觉像是一种反模式。我在这里缺少什么?


背景

在我的 React 应用程序中显示的状态是一个分层结构,实际上嵌套了六到七层深。

在顶层,它是一个简单的列表。但是每个列表项都有这样的结构:

Item = {
    priorChildren: List<Item>
    content: List<NonHierarchicalData>
    laterChildren: List<Item>
}
Run Code Online (Sandbox Code Playgroud)

这些Items 可以以用户认为合适的任何方式嵌套。

  • 每个都Item应该通过上下文菜单提供最多五个操作,具体取决于其在层次结构中的位置(可能不超过 10 个排列)。
  • 每个NonHierarchicalData还应根据其数据提供一个上下文菜单,最多可能有 30 个菜单项。由于数据包含合并到菜单项中的用户输入,因此每个上下文菜单都可能是唯一的。

一个现实状态可能包含大约 30 或 40 个这样的Items,每个都有 5 到 10 个NonHierarchicalData元素。根据这些数字,我最终可以得到 ca。250 个不同的上下文菜单,包含 5000 多个菜单项。在任何给定的时间,用户可能只会在选择一个动作之前打开一个或两个上下文菜单,从而触发状态更改,从而触发重新渲染。

挑战

一方面,有一些很棒的现有库,例如react-contextmenu,它们期望所有上下文菜单变体都是预先知道的,并且作为 DOM 的一部分创建,仅在需要时才可见。

另一方面,创建数百个包含数千个条目的菜单只是为了显示其中的几十个,然后在下一次状态更改后不可避免地重新创建它们,这感觉违反直觉。

Car*_*ten 5

在它的版本中v2.3.1react-contextmenu提供了一个connectMenu()助手,能够将触发器的道具传递给单个注册的菜单,以更改其渲染。 但是,将来可能会重构,因此在较新版本中谨慎使用。


简而言之:您可以为 a 使用单个包装器组件,ContextMenu并使其能够通过上述connectMenu()帮助程序接收它的触发器道具。

const MENU_ID = 'some-identifier';
const DynamicMenu = (props) => {
  const { id, trigger } = props;
  return (
    <ContextMenu id={id}>
      {trigger && <MenuItem>{trigger.itemLabel}</MenuItem>}
    </ContextMenu>
  );
};
const ConnectedMenu = connectMenu(MENU_ID)(DynamicMenu);
Run Code Online (Sandbox Code Playgroud)

a ContextMenu(here ConnectedMenu) 的连接包装器只需在某个父组件中包含一次,而多个触发器可以将它用于具有不同内容的菜单。请注意,ContextMenuTriggerconnectprop 必须转发其全部props或至少与正在构建的菜单相关的部分。例如

const DynamicMenuExample = (props) => (
  <div>
    <ContextMenuTrigger id={MENU_ID} itemLabel='one'
        connect={props => props}>
      {'Click me for a menu with a "one" item.'}
    </ContextMenuTrigger>
    <ContextMenuTrigger id={MENU_ID}
        connect={() => ({itemLabel: 'other'})}>
      {'Click me for a menu with an "other" item.'}
    </ContextMenuTrigger>
    <ContextMenuTrigger id={MENU_ID} itemLabel='third'
        connect={props => ({ itemLabel: props.itemLabel})}>
      {'Click me for a menu with a "third" item.'}
    </ContextMenuTrigger>
    <ConnectedMenu />
  </div>
);
Run Code Online (Sandbox Code Playgroud)

有关完整示例,请参阅项目的DynamicMenu 演示