如何使用 Hooks 和 Portals 在 React 中创建高效的 Modal 组件

2025-06-08

如何使用 Hooks 和 Portals 在 React 中创建高效的 Modal 组件

模态框是一种常见的用户体验元素。模态框是显示在当前页面顶部的对话框/弹出窗口。你肯定在你的网站上使用过弹窗和通知。对一些人来说,如果弹窗不能正常运作,会非常烦人😫。网站必须拥有良好的用户界面/用户体验。

在本文中,我们将从头开始创建一个高效的Modal 组件🤩,而不使用任何库。

演示链接🖤:https://react-reusable-components.vercel.app/

我们的主要目标是创建一个高效的模式,

  • 布局良好
  • 在父组件中使用溢出时不会破坏 UI
  • 可以动态呈现内容
  • 干净优雅的动画
  • 看起来不错(良好的用户界面)
  • 对用户有更多的控制权(比如点击外部可以关闭模​​式)等。

让我们开始吧!

如果您更喜欢视频格式,那么您可以观看此视频📽👇

使用以下方式创建您的 react-app,

npx create-react-app react-modal

本教程将使用Sass,因此请确保你已经安装了 node-sass 包。安装方法如下:

npm install node-sass

让我们创建基本模态组件

打开 App.js 文件。

清理🧹不必要的代码。

现在在 App.js 文件中创建一个按钮来打开和关闭模式,就像这样👇

<div className="App">
      <button>
        Modal
      </button>
</div>
Enter fullscreen mode Exit fullscreen mode

创建一个状态来打开和关闭模式。

写下以下👇代码:

import { useState } from "react";
import "./App.scss";
function App() {
const [modal, setModal] = useState(false);
const Toggle = () => setModal(!modal);
return (
<div className="App">
<button className="clickme" onClick={() => Toggle()}>
Modal
</button>
</div>
);
}
export default App;
view raw Modal.js hosted with ❤ by GitHub
import { useState } from "react";
import "./App.scss";
function App() {
const [modal, setModal] = useState(false);
const Toggle = () => setModal(!modal);
return (
<div className="App">
<button className="clickme" onClick={() => Toggle()}>
Modal
</button>
</div>
);
}
export default App;
view raw Modal.js hosted with ❤ by GitHub

第 6 行:包含模态状态,最初为假。

第 7 行: Toggle 方法,用于将模式状态从 false 切换为
true,反之亦然。

第 11 行:确保将Toggle()方法连接到

按钮的 onClick。

接下来创建Modal.js文件并写入以下👇代码:

const Modal = () => {
  return (
    <div>
      Modal
    </div>
  );
};

export default Modal;
Enter fullscreen mode Exit fullscreen mode

现在在文件中导入 Modal App.js

import { useState } from "react";
import "./App.scss";
import Modal from "./Components/Modal";
function App() {
const [modal, setModal] = useState(false);
const Toggle = () => setModal(!modal);
return (
<div className="App">
<button className="clickme" onClick={() => Toggle()}>
Modal
</button>
<Modal show={modal} />
</div>
);
}
export default App;
view raw Modal.js hosted with ❤ by GitHub
import { useState } from "react";
import "./App.scss";
import Modal from "./Components/Modal";
function App() {
const [modal, setModal] = useState(false);
const Toggle = () => setModal(!modal);
return (
<div className="App">
<button className="clickme" onClick={() => Toggle()}>
Modal
</button>
<Modal show={modal} />
</div>
);
}
export default App;
view raw Modal.js hosted with ❤ by GitHub

第 17 行:这里我们导入了Modal组件,并传递了
props 中显示的模态状态。

现在打开Modal.js并写入以下代码👇

import Close from "./times-solid.svg";
const Modal = ({ show }) => {
return (
<>
{
show ?
<div
className="modalContainer"
>
<div className="modal" >
<header className="modal_header">
<h2 className="modal_header-title"> Modal Title </h2>
<button className="close" >
<img src={Close} alt="close" />
</button>
</header>
<main className="modal_content">
This is Modal Content
</main>
<footer className="modal_footer">
<button className="modal-close" >
Cancel
</button>
<button className="submit">Submit</button>
</footer>
</div>
</div>
: null
}
</>
);
};
export default Modal;
view raw Modal2.js hosted with ❤ by GitHub
import Close from "./times-solid.svg";
const Modal = ({ show }) => {
return (
<>
{
show ?
<div
className="modalContainer"
>
<div className="modal" >
<header className="modal_header">
<h2 className="modal_header-title"> Modal Title </h2>
<button className="close" >
<img src={Close} alt="close" />
</button>
</header>
<main className="modal_content">
This is Modal Content
</main>
<footer className="modal_footer">
<button className="modal-close" >
Cancel
</button>
<button className="submit">Submit</button>
</footer>
</div>
</div>
: null
}
</>
);
};
export default Modal;
view raw Modal2.js hosted with ❤ by GitHub

第三行:从道具上解构表演。

第 7 行:仅当显示状态为真时,我们才会显示模态框。

第 9 行至第 30 行:这是整个模态布局

  • ModalContainer div 包含模态框
  • 在模态 div 中,有一个标题,其中包含模态标题关闭按钮(您可以使用任何图标作为关闭按钮)。
  • 主标签包含模式的内容。
  • 页脚有两个按钮,一个是提交,另一个是取消

现在,当您按下按钮时,模式将显示,再次按下时它将关闭模式。

首先让我们为我们的模态框添加一些样式。

创建Modal.scss文件并将其导入Modal.js文件中。

将此样式复制并粘贴到Modal.scss文件中。

.modalContainer {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba($color: #000000, $alpha: 0.35);
display: flex;
justify-content: center;
align-items: center;
.modal {
width: 30vw;
height: auto;
background-color: #fff;
padding: 2rem;
border-radius: 20px;
background-color: rgba(255, 255, 255, 0.35);
backdrop-filter: blur(5px);
box-shadow: 0 0 1rem 0 rgba(0, 0, 0, 0.2);
&_header {
position: relative;
border-bottom: 1px solid #dddddd;
&-title {
text-align: center;
}
.close {
position: absolute;
top: 0;
right: 0;
background: transparent;
img {
width: 1rem;
height: auto;
transition: all 0.3s;
}
&:hover {
img {
transform: scale(1.1);
}
}
}
}
&_content {
border-bottom: 1px solid #dddddd;
padding: 2rem 0;
}
&_footer {
padding: 2rem 0;
padding-bottom: 0;
button {
float: right;
padding: 0.5rem;
border-radius: 8px;
}
.modal-close {
background-color: transparent;
font-weight: 600;
&:hover {
color: rgba(54, 67, 72, 0.8);
}
}
.submit {
margin-right: 1rem;
background-color: #364348;
color: #fff;
&:hover {
background-color: rgba(54, 67, 72, 0.8);
}
}
}
}
}
view raw ModalSass.scss hosted with ❤ by GitHub
.modalContainer {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba($color: #000000, $alpha: 0.35);
display: flex;
justify-content: center;
align-items: center;
.modal {
width: 30vw;
height: auto;
background-color: #fff;
padding: 2rem;
border-radius: 20px;
background-color: rgba(255, 255, 255, 0.35);
backdrop-filter: blur(5px);
box-shadow: 0 0 1rem 0 rgba(0, 0, 0, 0.2);
&_header {
position: relative;
border-bottom: 1px solid #dddddd;
&-title {
text-align: center;
}
.close {
position: absolute;
top: 0;
right: 0;
background: transparent;
img {
width: 1rem;
height: auto;
transition: all 0.3s;
}
&:hover {
img {
transform: scale(1.1);
}
}
}
}
&_content {
border-bottom: 1px solid #dddddd;
padding: 2rem 0;
}
&_footer {
padding: 2rem 0;
padding-bottom: 0;
button {
float: right;
padding: 0.5rem;
border-radius: 8px;
}
.modal-close {
background-color: transparent;
font-weight: 600;
&:hover {
color: rgba(54, 67, 72, 0.8);
}
}
.submit {
margin-right: 1rem;
background-color: #364348;
color: #fff;
&:hover {
background-color: rgba(54, 67, 72, 0.8);
}
}
}
}
}
view raw ModalSass.scss hosted with ❤ by GitHub

这将使你的模态看起来更好。

第 21 行:通过应用此功能,backdrop-filter您可以使它
看起来像霜玻璃。

让我们在模态框中添加关闭事件

App.js文件中将切换方法作为模式中的道具传递,就像这样👇

<Modal show={modal} title="My Modal" close={Toggle}/>
Enter fullscreen mode Exit fullscreen mode

打开Modal.js文件并从道具中解构关闭。

import Close from "./times-solid.svg";
const Modal = ({ show, close }) => {
return (
<>
{
show ?
<div
className="modalContainer"
onClick={() => close()}
>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<header className="modal_header">
<h2 className="modal_header-title">Modal Title</h2>
<button className="close" onClick={() => close()}>
<img src={Close} alt="close" />
</button>
</header>
<main className="modal_content">This is modal content</main>
<footer className="modal_footer">
<button className="modal-close" onClick={() => close()}>
Cancel
</button>
<button className="submit">Submit</button>
</footer>
</div>
</div>
: null
}
</>
);
};
export default Modal;
view raw Modal2.js hosted with ❤ by GitHub
import Close from "./times-solid.svg";
const Modal = ({ show, close }) => {
return (
<>
{
show ?
<div
className="modalContainer"
onClick={() => close()}
>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<header className="modal_header">
<h2 className="modal_header-title">Modal Title</h2>
<button className="close" onClick={() => close()}>
<img src={Close} alt="close" />
</button>
</header>
<main className="modal_content">This is modal content</main>
<footer className="modal_footer">
<button className="modal-close" onClick={() => close()}>
Cancel
</button>
<button className="submit">Submit</button>
</footer>
</div>
</div>
: null
}
</>
);
};
export default Modal;
view raw Modal2.js hosted with ❤ by GitHub

第三行:从道具中解构 Close。

我们在 3 处添加了 close 方法:
第 16 行:关闭按钮处。
第 22 行:取消按钮处。

第 11 行:我们在这里也添加了 close 方法。为什么?因为每当用户点击外部时,都应该关闭模态框。所以在这里,当用户点击 modalContainer 时,它就会关闭模态框。

第 13 行:在这里我们必须停止模态框中的点击事件,否则它将关闭,因此我们使用了 e.stopPropagation()。

提示:您还可以添加事件监听器,并添加
当用户点击 Esc 键时关闭
模态框的功能。(这有利于用户体验)

让我们使用 Portals 来渲染 Modal 组件

什么是门户

  • 门户提供了一种一流的方法,将子项渲染到父组件的 DOM 层次结构之外的 DOM 节点中。

为什么🤔我们应该使用门户?

  • 有时,当我们在父组件中使用overflowz-index属性时,我们需要子组件(如模态框或对话框)在视觉上打破容器,而门户可以非常方便地做到这一点,因为它将子项呈现在 DOM 层次结构之外。

创建门户的语法✍👇

ReactDOM.createPortal

元素,
要渲染此元素的 DOM 节点

因此,让我们在 Modal 组件中实现门户。

要使用门户,我们必须在 dom 中再创建一个元素。
通常,我们的整个应用程序都会渲染在 id 为 root 的 div 中。

打开index.html文件。
在根 div 上方再创建一个 id 为modal 的div 。
就像这样👇

<div id="modal" />
<div id="root" />
Enter fullscreen mode Exit fullscreen mode

打开Modal.js文件并像这样编辑它,

import ReactDOM from "react-dom";
import "./modal.scss";
import Close from "./times-solid.svg";
const Modal = ({ show, close }) => {
return ReactDOM.createPortal(
<>
{
show ?
<div
className="modalContainer"
onClick={() => close()}
>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<header className="modal_header">
<h2 className="modal_header-title">Modal Title</h2>
<button className="close" onClick={() => close()}>
<img src={Close} alt="close" />
</button>
</header>
<main className="modal_content">This is modal content</main>
<footer className="modal_footer">
<button className="modal-close" onClick={() => close()}>
Cancel
</button>
<button className="submit">Submit</button>
</footer>
</div>
</div>
: null
},
document.getElementById("modal")
</>
);
};
export default Modal;
view raw Modal2.js hosted with ❤ by GitHub
import ReactDOM from "react-dom";
import "./modal.scss";
import Close from "./times-solid.svg";
const Modal = ({ show, close }) => {
return ReactDOM.createPortal(
<>
{
show ?
<div
className="modalContainer"
onClick={() => close()}
>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<header className="modal_header">
<h2 className="modal_header-title">Modal Title</h2>
<button className="close" onClick={() => close()}>
<img src={Close} alt="close" />
</button>
</header>
<main className="modal_content">This is modal content</main>
<footer className="modal_footer">
<button className="modal-close" onClick={() => close()}>
Cancel
</button>
<button className="submit">Submit</button>
</footer>
</div>
</div>
: null
},
document.getElementById("modal")
</>
);
};
export default Modal;
view raw Modal2.js hosted with ❤ by GitHub

第 1 行:导入 ReactDom。

第 6 行:返回使用创建门户后ReactDom.createPortal,作为第一个参数,我们传递了整个模态组件,对于第二个参数,我们传递了想要渲染它的 dom 节点。

第 34 行:我们希望在 id 为 modal 的 div 中呈现我们的组件。

让我们使模态内容动态化:

打开App.js文件并将标题作为道具传递,并将内容传递到组件内部,如下所示,

<Modal show={modal} title="My Modal" close={Toggle}>
        This is Modal content
</Modal>
Enter fullscreen mode Exit fullscreen mode

这里我们将标题作为道具传递,将模态内容作为子项传递。

打开Modal.js并书写,

(最终的 Modal.js 代码)

import ReactDOM from "react-dom";
import "./modal.scss";
import Close from "./times-solid.svg";
const Modal = ({ show, close, title, children }) => {
return ReactDOM.createPortal(
<>
{
show ?
<div
className="modalContainer"
onClick={() => close()}
>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<header className="modal_header">
<h2 className="modal_header-title"> {title} </h2>
<button className="close" onClick={() => close()}>
<img src={Close} alt="close" />
</button>
</header>
<main className="modal_content"> {children} </main>
<footer className="modal_footer">
<button className="modal-close" onClick={() => close()}>
Cancel
</button>
<button className="submit">Submit</button>
</footer>
</div>
</div>
: null
},
document.getElementById("modal")
</>
);
};
export default Modal;
view raw Modal2.js hosted with ❤ by GitHub
import ReactDOM from "react-dom";
import "./modal.scss";
import Close from "./times-solid.svg";
const Modal = ({ show, close, title, children }) => {
return ReactDOM.createPortal(
<>
{
show ?
<div
className="modalContainer"
onClick={() => close()}
>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<header className="modal_header">
<h2 className="modal_header-title"> {title} </h2>
<button className="close" onClick={() => close()}>
<img src={Close} alt="close" />
</button>
</header>
<main className="modal_content"> {children} </main>
<footer className="modal_footer">
<button className="modal-close" onClick={() => close()}>
Cancel
</button>
<button className="submit">Submit</button>
</footer>
</div>
</div>
: null
},
document.getElementById("modal")
</>
);
};
export default Modal;
view raw Modal2.js hosted with ❤ by GitHub

第 5 行从道具中title解构。children

第 17 行:在花括号中插入标题。

第 22 行:使用花括号渲染子项。

现在,如果您想在模态中添加一个小动画,您可以观看视频,或者您可以转到 git 存储库并阅读📚代码。

如果您有任何疑问,请在评论中提问

感谢阅读😄

欢迎访问我的 YouTube 频道:

@CodeBucks

链接:https://dev.to/codebucks/how-to-create-an-efficient-modal-component-in-react-using-hooks-and-portals-360p
PREV
Redux 是什么?简单解释一下!
NEXT
如何打造开发者个人品牌:分步指南