Portals
portal 可以将子节点渲染到父组件之外的其他 DOM 节点中。
语法:
ReactDom.createPortal(child, container)
用法:
通常来说,组件 render 返回的元素会被挂载到 DOM 节点中离其父组件最近的节点;有时候,将子元素插入到 DOM 节点中 的不同位置也是有好处的:
render() {
// React 并*没有*创建一个新的 div。它只是把子元素渲染到 `domNode` 中。
// `domNode` 是一个可以在任何位置的有效 DOM 节点。
return ReactDOM.createPortal(
this.props.children,
domNode,
)
}
应用
一个 portal 的典型用例是当父组件有 overflow: hidden
或 z-index
样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框等:
<style>
#root {
overflow: hidden;
}
</style>
<div id="root"></div>
<div id="modal"></div>
import React, { useState, useEffect } from "react";
import { createPortal } from "react-dom";
// 1. 获取 DOM 的根节点
const rootEl = document.getElementById("root");
const modalRootEl = document.getElementById("modal");
const Modal = ({ children }) => {
const el = document.createElement("div");
useEffect(() => {
// 2. 在 Modal 挂载时添加 el 节点
modalRootEl.appendChild(el);
return () => {
// 在卸载时移除 el 节点
modalRootEl.removeChild(el);
};
}, []);
// 3. 将 children 属性挂载到 el 节点
return createPortal(children, el);
};
const modalStyle = {
backgroundColor: "rgba(0, 0, 0, 0.5)",
position: "fixed",
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
top: 0,
left: 0,
};
const Portals = () => {
const [showModal, setShowModal] = useState(false);
const handleShow = () => {
setShowModal(true);
};
const handleHide = () => {
setShowModal(false);
};
const modal = showModal ? (
<Modal>
<div style={modalStyle}>
<div>With a portal, we can render content into a different</div>
<button onClick={handleHide}>hide modal</button>
</div>
</Modal>
) : null;
return (
<div>
This div has overflow:hidden.
<button onClick={handleShow}>show modal</button>
{modal}
</div>
);
};
export default Portals;
通过 Portal 进行事件冒泡
DOM 中 modal 的兄弟节点 root 可以捕获来自 Protal 冒泡的事件:
import React, { useState, useEffect, memo } from "react";
import { Component } from "react";
import { createPortal } from "react-dom";
const rootEl = document.getElementById("root");
const modalRootEl = document.getElementById("modal");
const Modal = ({ children }) => {
const el = document.createElement("div");
useEffect(() => {
modalRootEl.appendChild(el);
return () => {
modalRootEl.removeChild(el);
};
});
return createPortal(children, el);
};
const modalStyle = {
backgroundColor: "rgba(0, 0, 0, 0.5)",
position: "fixed",
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
top: 0,
left: 0,
};
const Portals = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount((count) => count + 1);
};
return (
<div onClick={handleClick}>
<div>count:{count}</div>
Open up the browser DevTools to observe that the button is not a child of
the div with the onClick handler.
<Modal>
<Child />
</Modal>
</div>
);
};
const Child = () => {
return (
<div style={modalStyle}>
<button>click</button>
</div>
);
};
export default Portals;
在父组件里捕获一个来自 portal 冒泡上来的事件,使之能够在开发时具有不完全依赖于 portal 的更为灵活的抽象。例如,如果你在渲染一个 <Modal />
组件,无论其是否采用 portal 实现,父组件都能够捕获其事件。