React Hook 手寫 Modal 模態框組件

前言

Modal (模態框) 是 web 開發中十分常見的組件,即從頁面中彈出的對話框。
今天咱們一塊兒來用 React Hook 手寫 Modal 模態框組件,最終實現的效果以下:
modal.pngcss

環境準備

本文代碼在 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 組件

清楚 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)

modal2.png

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);

打開模態框時效果以下:
modal3.png

參考文章

相關文章
相關標籤/搜索