如何使用 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>
创建一个状态来打开和关闭模式。
写下以下👇代码:
| 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; |
| 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; |
第 6 行:包含模态状态,最初为假。
第 7 行: Toggle 方法,用于将模式状态从 false 切换为
true,反之亦然。
第 11 行:确保将Toggle()方法连接到
按钮的 onClick。
接下来创建Modal.js文件并写入以下👇代码:
const Modal = () => {
return (
<div>
Modal
</div>
);
};
export default Modal;
现在在文件中导入 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; |
| 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; |
第 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; |
| 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; |
第三行:从道具上解构表演。
第 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); | |
| } | |
| } | |
| } | |
| } | |
| } |
| .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); | |
| } | |
| } | |
| } | |
| } | |
| } |
这将使你的模态看起来更好。
第 21 行:通过应用此功能,backdrop-filter您可以使它
看起来像霜玻璃。
让我们在模态框中添加关闭事件
在App.js文件中将切换方法作为模式中的道具传递,就像这样👇
<Modal show={modal} title="My Modal" close={Toggle}/>
打开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; |
| 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; |
第三行:从道具中解构 Close。
我们在 3 处添加了 close 方法:
第 16 行:关闭按钮处。
第 22 行:取消按钮处。
第 11 行:我们在这里也添加了 close 方法。为什么?因为每当用户点击外部时,都应该关闭模态框。所以在这里,当用户点击 modalContainer 时,它就会关闭模态框。
第 13 行:在这里我们必须停止模态框中的点击事件,否则它将关闭,因此我们使用了 e.stopPropagation()。
提示:您还可以添加事件监听器,并添加
当用户点击 Esc 键时关闭
模态框的功能。(这有利于用户体验)
让我们使用 Portals 来渲染 Modal 组件
什么是门户?
- 门户提供了一种一流的方法,将子项渲染到父组件的 DOM 层次结构之外的 DOM 节点中。
为什么🤔我们应该使用门户?
- 有时,当我们在父组件中使用
overflow或z-index属性时,我们需要子组件(如模态框或对话框)在视觉上打破容器,而门户可以非常方便地做到这一点,因为它将子项呈现在 DOM 层次结构之外。
创建门户的语法✍👇
ReactDOM.createPortal
(
元素,
要渲染此元素的 DOM 节点
)
因此,让我们在 Modal 组件中实现门户。
要使用门户,我们必须在 dom 中再创建一个元素。
通常,我们的整个应用程序都会渲染在 id 为 root 的 div 中。
打开index.html文件。
在根 div 上方再创建一个 id 为modal 的div 。
就像这样👇
<div id="modal" />
<div id="root" />
打开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; |
| 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; |
第 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>
这里我们将标题作为道具传递,将模态内容作为子项传递。
打开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; |
| 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; |
第 5 行:从道具中title解构。children
第 17 行:在花括号中插入标题。
第 22 行:使用花括号渲染子项。
现在,如果您想在模态中添加一个小动画,您可以观看视频,或者您可以转到 git 存储库并阅读📚代码。
如果您有任何疑问,请在评论中提问
感谢阅读😄
欢迎访问我的 YouTube 频道:
后端开发教程 - Java、Spring Boot 实战 - msg200.com