如何使用 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 频道: