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);
打開模態框時效果以下: