如何在 React 16 中使用 ReactDOM.createPortal()?

Posted

技术标签:

【中文标题】如何在 React 16 中使用 ReactDOM.createPortal()?【英文标题】:How to use ReactDOM.createPortal() in React 16? 【发布时间】:2018-03-05 17:46:22 【问题描述】:

我正在使用 React 16 并且需要门户,但我无法找到有关此功能的任何文档。有谁知道这个怎么用吗?

https://github.com/facebook/react/pull/10675

感谢您的建议。

【问题讨论】:

facebook.github.io/react/docs/portals.html 文档非常清楚地解释了如何使用它们...您阅读了吗? @ndugger 是的,文档现在已经足够好了 :)。但是OP在两天前问了这个问题。 React v16 那时还没有发布。它于今天发布,文档也在今天提供。 @HardikModha 好,那么我们可以删除这个不再需要的问题。 我想说,让这个问题存在,因为@HardikModha 的回答强调了阅读文档时可以忽略的重要部分。 【参考方案1】:

门户是通过在组件的render 方法中调用createPortal 方法来创建的。

render() 
  return (
    <div>
      ReactDOM.createPortal(this.renderPortal(), this.props.portalEl)
    </div>
  )

renderPortal 应该返回要在门户内呈现的内容,而portalEl 是一个将接收内容的外部 DOM 元素。

最近有人在推特上说可以在React tests 找到有关门户的信息。

【讨论】:

【参考方案2】:

React v16 几个小时前刚刚发布(耶!!),它正式支持 Portal

什么是传送门?从那里有多久了?

门户提供了一种一流的方式来将子级呈现到存在于父组件的 DOM 层次结构之外的 DOM 节点中。

Portal 在 React 社区中并不是一个新概念。许多库都支持这种功能。例如react-portal 和react-gateway。

渲染任何 React 应用时会发生什么?

通常,在渲染任何 React 应用程序时,都会使用单个 DOM 元素来渲染整个 React 树。

class HelloReact extends React.Component 
   render() 
       return (
          <h1>Hello React</h1>
       );
   


ReactDOM.render(<HelloReact />, document.getElementById('root'));

如您所见,我们正在将我们的 react 组件渲染成一个 DOM 元素,其 id 为 root

什么是 Portal,为什么需要它?为什么会在那里?

门户是一种在父组件的主 DOM 层次结构之外渲染 React 子级的方法不会丢失反应上下文。我之所以强调它,是因为像react-router、redux 这样的非常流行的库大量使用了反应上下文。所以使用Portal 时的上下文可用性非常有帮助。

根据react 文档,

门户的典型用例是父组件具有溢出:隐藏或 z-index 样式,但您需要子组件在视觉上“突破”其容器。例如,对话框、悬停卡片和工具提示。

因此,使用门户,您可以在需要时将并行反应树渲染到另一个 DOM 节点上。即使它在不同的 DOM 节点中呈现,父组件也可以捕获未捕获的事件。请参阅文档本身中提供的 codepen。

下面的例子应该会给你更多的想法:

// index.html
<html>
    <body>
        <div id="root"></div>
        <div id="another-root"></div>
    </body>
</html>

// index.jsx
const mainContainer = document.getElementById('root');
const portalContainer = document.getElementById('another-root');

class HelloFromPortal extends React.Component 
    render() 
         return (
           <h1>I am rendered through a Portal.</h1>
         );
    


class HelloReact extends React.Component 
    render() 
        return (
             <div>
                 <h1>Hello World</h1>
                  ReactDOM.createPortal(<HelloFromPortal />, portalContainer) 
             </div>
        );
    


ReactDOM.render(<HelloReact />, mainContainer);

https://codesandbox.io/s/62rvxkonnw

您可以使用 devtools 检查元素并看到 &lt;h1&gt;I am rendered through a Portal.&lt;/h1&gt; 呈现在 #another-root 标记内,而 &lt;h1&gt;Hello World&lt;/h1&gt; 呈现在 #root 标记内。

希望这会有所帮助:)

更新:回复@PhillipMunin's comment。

ReactDOM.renderReactDOM.createPortal?

    即使通过门户呈现的组件被呈现在其他地方(当前容器根目录之外),它仍然作为同一父组件的子组件存在。 (谁调用了ReactDOM.createPortal)所以孩子身上的任何事件都会传播给父母。 (Ofc,如果您手动停止事件的传播,这将不起作用。)

    在通过门户呈现的组件内部可以访问相同的上下文。但在我们直接ReactDOM.render 的情况下则不然。

我创建了另一个演示来说明我的观点。 https://codesandbox.io/s/42x771ykwx

【讨论】:

这与写 ReactDOM.render(&lt;HelloFromPortal /&gt;, portalContainer) 而不是ReactDOM.createPortal(...) 有何不同?我在代码沙箱中对其进行了测试 - 似乎没有什么不同:codesandbox.io/s/88p7lwn3l @PhilippMunin 除了保存在门户中的上下文之外,事件还会向上传播。尝试在&lt;HelloReact&gt; 的最上面的div 中添加一个点击监听器,并看到当使用render 时来自&lt;HelloFromPortal&gt; 的点击不会向上传播。使用createPortal 时,来自门户内部的点击会传播到父组件。 ReactDOM.unstable_renderSubtreeIntoContainer 而不是渲染呢?它应该传递上下文,不确定事件 Dan 不推荐。 twitter.com/dan_abramov/status/774591126602911746 @PhilippMunin 我已经更新了我的答案来回答你的评论。【参考方案3】:

对于任何想知道的人,这是我在 Typescript 中实现自己的自定义 Portal 组件的方式

    import React from 'react'
    import ReactDOM from 'react-dom'

    const Portal: React.FC<PortalProps> = (props) => 
        return ReactDOM.createPortal(props.children, props.target)
    

    export interface PortalProps 
        target: Element;
        children: React.ReactNode;
    

    export default Portal

【讨论】:

【参考方案4】:

我认为我们可以通过在“根”元素之外创建一个外部 dom 节点来实现相同的功能

node = document.createElement('div')
document.body.appendChild(node)
ReactDOM.render(<Modal ...props />,node)

假设我们正在创建一个模态视图,我们可以为其创建一个包装器

import React,  useEffect  from 'react'
import ReactDOM from 'react-dom'
import Modal from './Modal'
let node = null
const ModalWrapper = (props) =>
    useEffect(()=>
        node && ReactDOM.render(<Modal ...props />,node)
    )
    useEffect(()=>
        node = document.createElement('div')
        document.body.appendChild(node)
        ReactDOM.render(<Modal ...props />,node)
    ,[])
    return(
        <script />
    )

这是一个例子Create Modal with React Portal and React Hooks from scratch without external library

【讨论】:

以上是关于如何在 React 16 中使用 ReactDOM.createPortal()?的主要内容,如果未能解决你的问题,请参考以下文章

ReactDOM.render:React 从 16.4.2 升级到 16.5.2 后,无法在未安装的组件上找到节点

ReactDOM RSS renderToString 生产错误

ReactDOM.hydrate() 在初始 s-s-r (React JS) 后不起作用

如何使用 ReactDOM 直接渲染 React 组件(ES6 API)?

如何使用 ReactDOM Render 渲染一个 React 组件

如何从 React 16 门户获取 ref?