假设我们需要将子元素渲染到 React 应用程序中。很容易吧?该子元素会被挂载到最近的 DOM 元素上,并因此在其中渲染。
render() {
return (
<div>
// Child to render inside of the div
</div>
);
}
但是!如果我们想将该子元素渲染到 div 外部 的其他地方呢?这可能很棘手,因为它打破了组件需要渲染为新元素并遵循父子层级结构的约定。父元素希望跟随其子元素。
这就是 React Portals 的用武之地。它们提供了一种在 DOM 层级结构外部渲染元素的方法,使元素更具可移植性。这可能不是一个完美的类比,但 Portals 有点像马里奥兄弟中的管道,可以将您从游戏的正常流程中传送到不同的区域。

Portals 的妙处在于?即使它们触发了独立于子元素父元素的自身事件,父元素仍在监听这些事件,这对于跨应用程序传递事件很有用。
我们将在本文中一起创建一个 Portal,然后将其转换为可重用组件。让我们开始吧!
我们正在构建的示例
这是一个关于 Portal 实际应用的相对简单的示例
查看 Kingsley Silas Chijioke 在 CodePen 上发布的笔 React Portal(@kinsomicrote)。
切换元素的可见性并不是什么新鲜事。但是,如果您仔细查看代码,您会注意到输出的元素受按钮控制,即使它不是按钮的直接后代。实际上,如果您将源代码与 DevTools 中的渲染输出进行比较,您将看到它们之间的关系。

因此,输出元素的父元素实际上会监听按钮点击事件,并允许插入子元素,即使它和按钮在 DOM 中是独立的同级元素。让我们分解创建此切换 Portal 元素的步骤,以了解其工作原理。
步骤 1:创建 Portal 元素
React 应用程序的第一行将告诉您 App 元素使用 ReactDOM 渲染在文档根目录上。如下所示;
ReactDOM.render(<App />, document.getElementById("root"));
我们需要将 App 元素放置在 HTML 文件中才能执行它。
<div id="App"></div>
Portals 也是类似的。创建 Portal 的第一步是在 HTML 文件中创建一个新的 div 元素。
<div id="portal"></div>
此 div 将用作我们的目标。我们使用 #portal
作为 ID,但它不必是那个。在此目标 div 内渲染的任何组件都将保持 React 的上下文。我们需要将 div 存储为变量的值,以便我们可以使用我们将创建的 Portal 组件。
const portalRoot = document.getElementById("portal");
看起来很像执行 App 元素的方法,对吧?
步骤 2:创建 Portal 组件
接下来,让我们将 Portal 设置为组件。
class Portal extends React.Component {
constructor() {
super();
// 1: Create a new div that wraps the component
this.el = document.createElement("div");
}
// 2: Append the element to the DOM when it mounts
componentDidMount = () => {
portalRoot.appendChild(this.el);
};
// 3: Remove the element when it unmounts
componentWillUnmount = () => {
portalRoot.removeChild(this.el);
};
render() {
// 4: Render the element's children in a Portal
const { children } = this.props;
return ReactDOM.createPortal(children, this.el);
}
}
让我们退一步,看看这里发生了什么。
我们在构造函数中创建一个新的 div 元素,并将其设置为 this.el
的值。当 Portal 组件挂载时,this.el
会作为子元素附加到 HTML 文件中我们添加它的那个 div。在我们的例子中,就是 <div id="portal"></div>
行。
DOM 树将如下所示。
<div> // Portal, which is also portalRoot
<div> // this.el
</div>
</div>
如果您不熟悉 React 并且对挂载和卸载元素的概念感到困惑,Jake Trent 给出了很好的解释。简而言之:挂载是元素插入 DOM 的时刻。
当组件卸载时,我们希望删除子元素以避免任何内存泄漏。我们将此 Portal 组件导入到另一个组件中以供使用,该组件是我们示例中包含标题和按钮的 div。这样做时,我们将 Portal 组件的子元素也一起传递。这就是为什么我们有 this.props.children
的原因。
步骤 3:使用 Portal
要渲染 Portal 组件的子元素,我们使用 ReactDOM.createPortal()
。这是一个特殊的 ReactDOM 方法,它接受子元素和我们创建的元素。要了解 Portal 的工作原理,让我们在 App 组件中使用它。
但是,在我们这样做之前,让我们先了解一下我们希望 App 如何运行的基本知识。当 App 加载时,我们希望显示文本和按钮——然后我们可以切换按钮以显示或隐藏 Portal 组件。
class App extends React.Component {
// The initial toggle state is false so the Portal element is out of view
state = {
on: false
};
toggle = () => {
// Create a new "on" state to mount the Portal component via the button
this.setState({
on: !this.state.on
});
};
// Now, let's render the components
render() {
const { on } = this.state;
return (
// The div where that uses the Portal component child
<div>
<header>
<h1>Welcome to React</h1>
</header>
<React.Fragment>
// The button that toggles the Portal component state
// The Portal parent is listening for the event
<button onClick={this.toggle}>Toggle Portal</button>
// Mount or unmount the Portal on button click
<Portal>
{
on ?
<h1>This is a portal!</h1>
: null
}
</Portal>
</React.Fragment>
</div>
);
}
}
由于我们希望打开和关闭 Portal,因此我们需要使用 组件状态 来管理切换。这基本上是一种方法,可以在点击事件上将 on
的状态设置为 true
或 false
。当 on
为 true 时,Portal 会被渲染;否则,我们什么也不渲染。
当 on
状态设置为 true
时,DOM 如下所示。

当 on
为 false
时,Portal 组件不会在根目录中渲染,因此 DOM 如下所示。

更多用例
模态框是 Portal 的完美候选者。实际上,React 文档使用它作为 Portal 工作原理的主要示例。
查看 Dan Abramov 在 CodePen 上发布的笔 示例:Portals(@gaearon)。
它与概念相同,其中创建了一个 Portal 组件,并使用状态将子元素附加到 Modal 组件。
我们甚至可以将来自外部源的数据插入模态框中。在此示例中,App 组件列出了使用 axios 从 API 获取的用户。
查看 Kingsley Silas Chijioke 在 CodePen 上发布的笔 React Portal 3(@kinsomicrote)。
工具提示怎么样?David Gilberston 有一个不错的演示。
查看 David Gilbertson 在 CodePen 上发布的笔 React Portal Tooptip(@davidgilbertson)。
J Scott Smith 展示了如何使用 Portals 来跳出定位。
他还有一个精巧的示例,演示了插入元素和管理状态。
总结
就是这样!希望这能为您提供关于 Portal 的坚实基础理解,包括它们是什么、它们做什么以及如何在 React 应用程序中使用它们。这个概念可能看起来微不足道,但能够将元素移动到 DOM 层级结构之外是一种方便的方法,可以使组件更具扩展性和可重用性……所有这些都指向使用 React 的核心优势。
更多信息
- React Portals 文档
- React v16 发行说明 – 引入 Portals 的版本
- CodePen 示例 – React Portals 搜索结果
哈哈,这个 Portal 是通往厄运的传送门。一旦代码库变得庞大,这种方法就不太好。
Portal 和 Fragment 有什么区别?
嗨,Jen,Fragment 和 Portal 完全不同。我希望我能解释清楚。Fragment 用于当我们希望有效地返回两个元素,但又不想用一个任意元素包裹它们时。例如,假设我们想要一个返回一个段落和一个按钮的组件,这是不允许的,因为我们返回了两个元素。
我们可以用一个 div 或其他东西来包裹它们,这样就只返回一个东西了,但由于某些原因,我们可能不想使用那个 div 包装器,仅仅是因为我们被迫返回一个东西。
<Fragment>
可以用作包装器,以便 JSX 看起来只返回一个东西,但 Fragment 不会渲染任何 DOM,所以它实际上使我们能够在没有包装器的情况下返回两个 DOM 节点。Portal 更像是组件决定在其 DOM 中渲染其输出位置的能力。例如,当我使用灯箱(模态框)时,我不希望灯箱 DOM 位于导致灯箱打开的按钮附近。我对此有一些(CSS)原因。由于我的应用程序一次只允许打开一个灯箱,并且由于我有我的 CSS 原因使其 DOM 位于链条的上方,所以我使用 Portal 将灯箱“渲染”为
<div id="root">
的同级元素。我不建议在大型应用程序中使用这种方法。