如何将一组 React 节点传递给不相关的组件

How to pass a set of React Nodes to unrelated components

我对 React 有疑问,这是 React 应用程序的简化版本。

在应用程序中,我想渲染一个固定的主菜单和一个可选的二级菜单,其内容由在路由中渲染的内部组件控制。

二级菜单也会在应用的移动版本的其他地方呈现。

function App() {
    return <Router>
        <PrimaryMenu/>

        <SecondaryMenu/>

        <LayoutContent/>
        {/* This block is rendered only on mobile devices */}
        <Responsive  {...Responsive.onlyMobile}>
            <SecondaryMenu/>
        </Responsive>
    </Router>;
}

LayoutContent 将根据路由规则呈现实际页面内容(使用 Page 组件),每个页面组件都可以像这样呈现自己的二级菜单(例如 page1 有自己的子菜单,page2 有另一个,第 3 页没有。)

<Page title='Page 1 - With secondary menu'>
    <SecondaryMenuItems>
        {/* I want this content as children of secondary menu in both mobile and desktop menubars */}
        <li>Page 1 item 1</li>
        <li>Page 1 item 2</li>
    </SecondaryMenuItems>
</Page>

我尝试使用 React 上下文来实现它,但是如果我将子节点存储在上下文中,则会触发无限渲染。我将其更改为在 <SecondaryMenuItems/> 组件中使用 id 属性,但该方法非常脆弱并且也有一些缺点。

这是我的 working example 它正在工作,但正如我所说,它非常脆弱:

此外,如果您切换到带有菜单的页面,然后转到第 3 页(没有菜单),则上一页菜单会保留在屏幕上。

如何用反应来完成这个?有建议的方法吗?

表达我的问题的更简单的方法是 "how to pass a set of react nodes between unrelated components (e.g. siblings components)"

更新

我已经根据收到的提示完成了我的工作示例,现在通过将 useRefReactDOM.createPortal 组合,我获得了示例中的最终结果。

在多个兄弟节点中呈现相同的children

问题问"how to pass a set of react nodes"。理想情况下,不要。如果您在层次结构中的某处呈现节点并打算在 别处 使用它们,则您可能使用了错误的策略。

如果您需要在不同的地方渲染相同的组件,请制作一个渲染组件的函数,并从两个地方调用它。换句话说,总是传递信息,而不是渲染的元素

在路由器内部渲染

在典型的单页应用程序中,路由器将呈现所有 (non-static) 组件。这就是示例应该如何完成的。路由组件 (LayoutContent) 应该负责直接渲染 "passed nodes" (SecondaryMenu)。

<Route path="/page1">
  <Page title="Page 1 - With secondary menu">
    <SecondaryMenu id="menu1"> {/* <- use SecondaryMenu instead of SecondaryMenuItems */}
      <li>Page 1 item 1</li>
      <li>Page 1 item 2</li>
    </SecondaryMenu>           {/* <- use SecondaryMenu instead of SecondaryMenuItems */}
  </Page>
</Route>

无法在路由器内部渲染时

如果由于某种原因路由组件无法直接呈现内容,那么单页应用程序(或路由)解决方案可能是错误的解决方案。该问题不包含有关为什么无法在路由器内呈现组件的任何信息,请随时编辑问题并发表评论以提供更多信息。

实现该示例的另一种方法是没有路由组件(即没有 LayoutContent),SecondaryMenu 检查页面路径并根据条件有条件地呈现适当的内容在那上面。 当有路由器组件为您执行此操作时,根据路径手动有条件地渲染似乎很愚蠢,我同意。解决方案就是根本不使用路由器。尝试在路由器中呈现 children 并传递它们具有强烈的代码气味。

在 React 分层布局中,如果需要相同的信息来决定在多个位置(在本例中为路径)渲染,将该信息向上移动到所有组件中最近的 parent 并传递它作为道具或上下文。

避免 ID 冲突

"What if I use a duplicate id for secondary menus?"

如果调用一个函数来渲染二级菜单而不是渲染并传递它,那么你可以在道具中传递一个菜单前缀,并在函数中使用这个菜单前缀。

function SecondaryMenuItems({ children, idPrefix, path }) {
  if (path == '/path1') {
    return (
      <ul id={`${idPrefix}-newlist`}>

按键上

"What if I forget a secondary menu key?"

React 键只需要是唯一的 within a rendered list。事实上,键只是一种优化,以防止 React 在每次传递时都必须 re-render 生成列表。如果你忘记包含一个键(或者选择了错误的键),React 每次都必须 re-render 列表,但这并不比这更严重。在简单的情况下,您不会注意到性能下降。

这是 React Portals 的用例。门户网站将允许您将页面中的二级菜单项呈现到存在于其他地方的二级菜单容器中

您需要做的就是在页面渲染中调用React.createPortal,传递渲染元素和目标节点渲染到,无论在DOM树

中的位置如何

我在此处使用门户编辑了您的示例 https://codesandbox.io/s/secondary-menu-example-vbm3x。这当然是一个基本示例,为了方便,您可能希望在单独的组件中抽象门户逻辑,and/or 从父级传递 dom 引用,而不是在 mount

上调用 getElementById