Skip to main content

Portal

Portal​

Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的方式。

ReactDOM.createPortal(child, container)

第一个参数是任何可渲染的 React 元素,第二个参数是一个 DOM 元素。

来看一个场景,如下:

import { useState } from "react";

function App() {
const [isShow, setIsShow] = useState(false);
return (
<div>
<h1>App</h1>
<button onClick={()=>setIsShow(!isShow)}>Show/Hide</button>
{isShow ? <Modal /> : null}
</div>
);
}

function Modal() {
return (
<div style={{
width : "450px",
height : "250px",
border : "1px solid",
position : "absolute",
left : "calc(50% - 225px)",
top : "calc(50% - 125px)",
textAlign : "center",
lineHeight : "250px"
}}>Modal</div>
)
}

export default App;

在上面的示例中,Modal 是一个模态框,App 根组件中能够控制该模态框组件是否显示。功能来讲并没有什么问题,但是从最终渲染出来的 html 结构上来讲,将整个模态框都放在根这个 div 中不是那么合适,生成的 html 结构上,这个模态框能够渲染到 Modal 那个 div 里面。

渲染后的结构如下:

<body>
<div id="root">
<div>
<h1>App</h1>
<button>Show/Hide</button>
<div style="width: 450px; height: 250px; border: 1px solid; position: absolute;
top: calc(50% - 125px); text-align: center; line-height: 250px;">
Modal
</div>
</div>
</div>
</body>

这样会产生一些问题,比如父组件设置了额外的样式可能会影响子组件。

这种情况下,可以使用 Portal 来解决这个问题。

使用​

Portal 的使用方式非常简单,只需要使用 createPortal 方法来指定渲染到哪个元素中即可。Portal 是和 React 渲染相关的,所以 createPortal 方法来自于 react-dom。

import {createPortal} from 'react-dom';

function Modal() {
return createPortal((<div style={{
width : "450px",
height : "250px",
border : "1px solid",
position : "absolute",
left : "calc(50% - 225px)",
top : "calc(50% - 125px)",
textAlign : "center",
lineHeight : "250px"
}}>Modal</div>),document.getElementById("modal"))
}

export default Modal;

渲染后的结构如下:

<body>
<div id="root">
<div>
<h1>App</h1>
<button>Show/Hide</button>
</div>
</div>
<div id="modal">
<div style="width: 450px; height: 250px; border: 1px solid; position: absolute;
top: calc(50% - 125px); text-align: center; line-height: 250px;">
Modal
</div>
</div>
</body>

Portal 的典型用例是当父组件有 overflow: hidden 或 z-index 样式时,但需要子组件能够在视觉上跳出其容器。例如,对话框、悬浮卡以及提示框。

事件冒泡​

以上面的例子为例,看上去模态框已经渲染到了 modal 这个元素里面,但是在 React 中事件冒泡是按照组件结构来进行冒泡的,在组件树中模态框组件仍然是在根组件中。

Portal

尽管 Portal 可以被放置在 DOM 树中的任何地方,但在任何其他方面,其行为和普通的 React 子节点行为一致。由于 portal 仍存在于 React 树, 且与 DOM 树中的位置无关,那么无论其子节点是否是 Portal,像 context 这样的功能特性都是不变的。

一个从 Portal 内部触发的事件会一直冒泡至包含 React 树的祖先,即便这些元素并不是 DOM 树中的祖先。