Modal (模态框) 是 web 开发中十分常见的组件,即从页面中弹出的对话框。
今天咱们一块儿来用 React Hook 手写 Modal 模态框组件,最终实现的效果以下:css
本文代码在 create-react-app 脚手架生成的项目中运行,react 版本:html
"react": "^16.12.0", "react-dom": "^16.12.0", "react-scripts": "3.3.0"
使用 Modal 组件,最重要就是控制它的打开和关闭。咱们先定义一个 modalVisible 的 state,当 modalVisible 为 true 时,模态框打开,反之关闭。
接下来就能够把 modalVisible 传入 Modal 组件来控制模态框的打开和关闭,同时 Modal 组件须要接收一个 onClose 的 prop,用来实如今组件中关闭模态框(例如点击蒙层时关闭模态框)。react
const [modalVisible, setModalVisible] = useState(false); const modalConfig = { visible: modalVisible, closeDialog: () => { setModalVisible(false); } }; <Modal {...modalConfig}></Modal>
Modal 组件应具有良好的扩展性,作到可自定义模态框的内容,例如模态框的标题、关闭按钮、肯定按钮等到。咱们把这部分自定义内容统统传入组件中。举个例子:web
const modalChildren = ( <div className="dialog"> <span onClick={() => setModalVisible(false)} className="closeBtn">x</span> <div>这是内容</div> </div> ); <Modal {...modalConfig}>{modalChildren}</Modal>
这部分自定义内容含有一个内容框,关闭按钮和文字内容,能够给它们添加一下样式:segmentfault
/* App.css */ .openBtn { margin-top: 240px; border: 1px solid dodgerblue; } .dialog { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); padding: 30px 30px; width: 200px; height: 200px; background-color: #fff; border-radius: 8px; } .closeBtn { position: absolute; right: 10px; top: 4px; font-size: 21px; }
完整的使用 Modal 组件的代码:app
import React, { useState } from 'react'; import './App.css'; import Modal from './components/modal'; function App() { const [modalVisible, setModalVisible] = useState(false); const modalConfig = { visible: modalVisible, closeModal: () => { setModalVisible(false); } }; const modalChildren = ( <div className="dialog"> <span onClick={() => setModalVisible(false)} className="closeBtn">x</span> <div>这是内容</div> </div> ); return ( <div className="App"> <button onClick={() => setModalVisible(true)} className="openBtn">open modal</button> <Modal {...modalConfig}>{modalChildren}</Modal> </div> ); } export default App;
清楚 Modal 组件须要接收的 props 以后,咱们就能够开始编写组件了。dom
首先,咱们须要给 modal 组件增长一个蒙层:编码
/* modal.css */ .modal { position: fixed; top: 0; left: 0; bottom: 0; right: 0; background: rgba(0, 0, 0, 0.3); z-index: 99; }
开始编写组件:spa
import React from 'react'; import './css/modal.css'; const Modal = (props) => { const { children, visible, closeModal } = props; function handleClick(event) { // 点击蒙层自己时关闭模态框,点击模态框的内容时不关闭 if (event.target === event.currentTarget) { closeModal(); } } const modal = ( <div className="modal" onClick={handleClick}> {children} </div> ); return <div>{visible && modal}</div>; }; export default React.memo(Modal);
上面咱们实现了经过visible来控制打开和关闭模态框,以及点击蒙层时关闭模态框。code
接下来,咱们要把模态框组件挂载在 body 的第一层中,而不是将模态框放置到父组件中。
由于模态框放置到父组件中很容易受到其余元素的干扰,仅是设置各个元素的 z-index 就使得代码难以维护。
咱们能够使用 React 的 Portal 来实现把模态框组件应该挂载在 body 上:
import { createPortal } from 'react-dom'; createPortal(/* 组件内容 */, document.body)
Modal 组件完整代码:
import React from 'react'; import './css/modal.css'; import { createPortal } from 'react-dom';` const Modal = (props) => { const { children, visible, closeModal } = props; function handleClick(event) { // 点击蒙层自己时关闭模态框,点击模态框的内容时不关闭 if (event.target === event.currentTarget) { closeModal(); } } const modal = createPortal( <div className="modal" onClick={handleClick}> {children} </div>, document.body ); return <div>{visible && modal}</div>; }; export default React.memo(Modal);
这样咱们就完整地开发出一个简单的易扩展的 Modal 组件了,其实它就是一个蒙层,内容都是经过父组件传进来。
当咱们完成上面的编码以后,咱们的模态框就能够实现显示/隐藏,而且处于 body 的顶层,可是还有一个问题,那就是若是 body 内容太长出现滚动时,滚动鼠标就会发现,模态框后边的背景也在滚动,这显然不是咱们指望的效果。如何应对这种状况呢?
解决办法很巧妙,就是在模态框打开时,咱们给 body 添加一个 overflow: hidden 的样式让 body 不滚动,关闭模态框时再去除这个样式。经过这样的方式,咱们就实如今模态框打开时背景不滚动的效果了。
明白了原理以后就开始修改代码了,咱们首先 在modal 组件中添加一个 bodyOverflow 的 state,用来保存 body 原始的 overflow 值,而后经过组件传入的 visible 来动态修改 body 的 overflow 值。
import React, { useEffect, useState } from 'react'; import './css/modal.css'; import { createPortal } from 'react-dom'; const Modal = (props) => { const { children, visible, closeModal } = props; const [bodyOverflow, setBodyOverflow] = useState(false); useEffect(() => { // 在第一次渲染时取 body 原始的 overflow 值 setBodyOverflow(window.getComputedStyle(document.body).overflow); }, []) useEffect(() => { // 根据 visible 来动态修改 body 的 overflow 值 if (visible) { document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = bodyOverflow; } }, [visible, bodyOverflow]) function handleClick(event) { // 点击蒙层自己时关闭模态框,点击模态框的内容时不关闭 if (event.target === event.currentTarget) { closeModal(); } } const modal = createPortal( <div className="modal" onClick={handleClick}> {children} </div>, document.body ); return <div>{visible && modal}</div>; }; export default React.memo(Modal);
打开模态框时效果以下: