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

Slb*_*box 25 javascript ecmascript-6 reactjs

我正在使用React 16并需要门户网站,但我无法找到有关此功能的任何文档.有谁知道怎么用呢?

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

谢谢你的建议.

Har*_*dha 39

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

什么是门户网站?它已经有多久了?

门户网站提供了一种将子项呈现为存在于父组件的DOM层次结构之外的DOM节点的第一类方法.

Portal反应社区中不是新概念.许多库都支持这种功能.例如react-portalreact-gateway.

渲染任何反应应用程序会发生什么?

通常,在呈现任何React应用程序时,使用单个DOM元素来呈现整个React树.

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

ReactDOM.render(<HelloReact />, document.getElementById('root'));
Run Code Online (Sandbox Code Playgroud)

如您所见,我们将react组件呈现为具有id的DOM元素root.

什么是Portal,为什么需要它?为什么会这样?

门户网站是一种在父组件的主DOM层次结构之外呈现React子节点而不会丢失响应上下文的方法.我正在强调它,因为非常流行的库像react-router,redux大量使用react上下文.因此,使用时的上下文可用性Portal非常有用.

根据反应文档,

门户网站的一个典型用例是当父组件具有溢出:隐藏或z-index样式时,但是您需要子容器在视觉上"突破"其容器.例如,对话框,hovercards和工具提示.

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

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

// 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);
Run Code Online (Sandbox Code Playgroud)

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

您可以使用devtools inspect元素并查看它<h1>I am rendered through a Portal.</h1>#another-root标记内部呈现,而<h1>Hello World</h1>#root标记内部呈现.

希望这可以帮助 :)

更新:回答@ PhillipMunin的评论.

ReactDOM.render和 之间有什么不同ReactDOM.createPortal

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

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

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

  • @PhilippMunin 除了保存在门户中的上下文之外,事件还会向上传播。尝试在 `&lt;HelloReact&gt;` 的最上层 div 添加一个点击监听器,并看到当使用 `render` 时,来自 `&lt;HelloFromPortal&gt;` 的点击不会向上传播。使用 `createPortal` 时,来自门户内部的点击会传播到父组件。 (4认同)
  • 这与编写`{ReactDOM.render(<HelloFromPortal />,portalContainer)}`而不是`{ReactDOM.createPortal(...)}`有什么不同?我在codesandbox中测试过它 - 似乎没有任何不同:https://codesandbox.io/s/88p7lwn3l (3认同)

Onl*_*yJS 8

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

node = document.createElement('div')
document.body.appendChild(node)
ReactDOM.render(<Modal {...props} />,node)
Run Code Online (Sandbox Code Playgroud)

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

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 />
    )
}
Run Code Online (Sandbox Code Playgroud)

这是一个使用 React Portal 和 React Hooks 从头开始​​创建 Modal 的示例,无需外部库

带门户的 React-modal