React Portal组件
Table of contents:
概念
createPortal
函数用于将组件渲染到DOM的不同位置,即”脱离“父组件的DOM。与react组件的渲染不同,createPortal
需要指定一个DOM节点去渲染。
createPortal函数
createPortal(children, domNode, key?)
参数
children
: 可以被react渲染的任意组件。domNode
: DOM节点,这个节点必须是一件存在的,如果在组件更新过程中,使用了一个不同的节点,会导致portal的重新创建。key
: 可选,portal的唯一表示
调用方式,下面这段代码会在document.body
元素中渲染出:<p>This child is placed in the document body.</p>
.
import { createPortal } from 'react-dom';
...
<div>
<p>This child is placed in the parent div.</p>
{createPortal(
<p>This child is placed in the document body.</p>,
document.body
)}
</div>
使用场景
createPortal
的使用场景通常是将一个组件渲染到DOM的不同位置,如模态框、全局提示组件等。
常见的的使用场景:
- 模态框。
- 模态对话框(Modal Dialogue Box,又叫做模式对话框),是指在用户想要对对话框以外的应用程序进行操作时,必须首先对该对话框进行响应。如单击【确定】或【取消】按钮等将该对话框关闭。
- 将React组件渲染到非React管理的DOM中。
下面展示了一个使用createPortal
函数将Footer组件渲染到#footer
div容器的示例。如下图,黑色边框内的元素是React组件(<App />
),红色边框的<Footer />
是react之外的元素,不受react管理,绿色边框内的元素是由react渲染的。
查看demo CodeSandbox
代码实现:
- index.html,在这段代码中有两个div元素:
#root
和#footer
。其中,#root
是react组件将会挂载的容器,而#footer
是react portal组件的容器。
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<div
id="footer"
style="border: 1px solid red; padding: 10px; margin-top: 20px;"
>
<ul>
Footer
</ul>
</div>
</body>
- App.js,在这个文件中,声明了一个React组件
<App />
,这个组件会被挂载到#root
容器中,在下面的代码中使用了createPortal
函数,将<Footer />
组件渲染到了#footer
容器。
import { createPortal } from "react-dom";
import Footer from "./Footer";
import "./styles.css";
export default function App() {
return (
<div className="App" style={{ border: "1px solid #333" }}>
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
{createPortal(
<Footer>
<div style={{ border: "1px solid green" }}>
<div>由react portal渲染</div>
<ol>
{[1, 2, 3, 4, 5].map((it) => {
return <li key={it}>item{it}</li>;
})}
</ol>
</div>
</Footer>,
document.querySelector("#footer")
)}
</div>
);
}
- Footer.js,这个文件声明了
<Footer />
组件,它将children
渲染出来。
import React from "react";
export default function Footer({ children }) {
return (
<div>
<div>{'<Footer>'}</div>
{children}
<div>{'</Footer>'}</div>
</div>
);
}
事件处理
portal组件的事件处理与其他react组件是一致的。如果在portal组件中点击,在父组件也是会捕获到的,即使它们不在同一个DOM层级中。
在下图中,展示了一个计数器,当点击App组件时计数器加1。当点击portal组件时,计数器也会加1。
代码如下:
import { useState } from "react";
import { createPortal } from "react-dom";
import Footer from "./Footer";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(0);
const handleCountIncrease = e => {
setCount(count+1);
console.log(e);
}
return (
<div onClick={handleCountIncrease} className="App" style={{ border: "1px solid #333" }}>
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<div>Count: {count}</div>
{createPortal(
<Footer>
<div style={{ border: "1px solid green" }}>
<div>由react portal渲染</div>
<ol>
{[1, 2, 3, 4, 5].map((it) => {
return <li key={it}>item{it}</li>;
})}
</ol>
</div>
</Footer>,
document.querySelector("#footer")
)}
</div>
);
}
与ReactDOM.render的不同
ReactDOM.render
函数是将React组件挂载到DOM一个节点中,createPortal
也是将组件渲染到DOM节点。
不同的是ReactDOM.render
函数会为每个组件创建一个上下文,即这些组件不能共享状态、事件。
createPortal
仅仅是将组件渲染到别的DOM节点,其他的如状态、事件处理都是不变。