React組件庫封裝初探--Modal

Madal組件實現基本簡介

clipboard.png

  • 相似於antd實現的modal組件,首先基本結構分析:node

    1. modal-mask遮罩層
    2. modal-warp內容包裝層
    3. modal主體內容層,包含:titlecontentfooterclose-btn
    • 固定定位佈局,全屏遮蓋顯示,因此內容自定義
  • 實現功能目標:react

    1. 兩種調用方式<Modal {...props}>一些內容</Modal>Modal.confirm({...props})
    2. 遮罩層、footerclose-btn的顯示與否,單擊是否可關閉
    3. 其餘必備功能

結構佈局攻克

  • 基本佈局
<div className="lwh-pirate-modal">
    <div className="lwh-modal-mask"/> // 遮罩層須要實現全屏遮罩
    // 內容層高度可自定義
    <div className={`lwh-modal-warp ${wrapClassName}`} style={{width}}>
        // 右上角關閉按鈕
       <div className="lwh-modal-close"><span>+</span></div>}
       // 主內容
       <div className="lwh-modal" style={{width,...style}}>
            <div className="lwh-modal-content">
                //title標題
                <div className="lwh-modal-header">
                    <div className="lwh-modal-title">{title}</div>
                </div>
                //body用戶輸入內容
                <div className="lwh-modal-body">
                    {children}
                </div>
                // footer底部按鈕
                <div className="lwh-modal-footer">
                    <div>
                        <Button type={okType}>{okText}</Button>
                        <Button type={cancelType}>{cancelText}</Button>
                    </div>
                </div>
            </div>
        </div>
   </div>
</div>
  1. 遮罩層全屏覆蓋編程

    • position: fixed定位
    • 全屏實現
    top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        z-index: 1000;
  2. 內容層segmentfault

    • position: fixed定位(modal-warp層)
    • warp層的佈局大小考慮
    1. 全屏:若是warp層實現全屏,因爲和mask層爲兄弟組件,致使warp層位於mask層之上,後面對mask層單擊可關閉功能易出現單擊不到,由於被全屏的warp層遮擋(可考慮使用事件委託,將單擊事件綁定至第一個父組件,經過判斷去除modal層的單擊,雖然單擊的仍是warp層);
    2. 大小跟隨modal:及設置warp層的大小恰好爲其內容modal,這樣就不會覆蓋所有mask層,可是,後期對傳入設置是否顯示mask層的功能有所影響(由於warp層不全屏,若是mask設置不顯示,會致使用戶能夠操做到底下主內容),可考慮mask的顯隱經過visibility: hidden控制.

基本功能邏輯實現

  • 基本對外接口(函數式)
const Modal = ({
    visible=false,
    style,
    width= 520,
    zIndex=1000,
    centered=false,
    title='title',
    footer,
    wrapClassName='',
    okText='肯定',
    okType='primary',
    cancelText='取消',
    cancelType='default',
    closable= true,
    onOk=() => {},
    onCancel=() => {},
    mask=true,
    maskClosable= true,
    children='Basic body'
}) => {
    return (
        visible ?
        ReactDOM.createPortal(<div>....</div>,document.querySelector('body')) : null
    )
}
  • 組件採用函數無狀態編程,Modal的顯隱由外部控制,內部不控制;
  • 組件的掛載使用ReactDOM.createPortal(child,container)掛載至body
  • 基本使用形式
import React,{ PureComponent } from 'react';
import { Modal,Button } from 'lwh_react';

export default class baseModal extends PureComponent {
    state = {
        visible: false
    }

    showModal = () => {
        this.setState({
            visible: true
        })
    }
    onCancel = () => {
        console.log('cancel')
        this.setState({
            visible: false
        })
    }
    onOk = () => {
        console.log('ok')
        this.setState({
            visible: false
        })
    }

    render() {
        const { visible } = this.state;
        return (
            <div>
                簡單基本用法:
                <Button onClick={this.showModal}>modal</Button>
                <Modal visible={visible} onCancel={this.onCancel} onOk={this.onOk}>
                    <div>modal提示內容</div>
                </Modal>
            </div>
        )
    }
}
  • 效果

clipboard.png


升級篇Modal.method()的攻克

  • 如何實現相似antd中modal.method的方法調用彈窗形式(且調用後返回一個引用包含{update, destroy}可控制彈窗):跨域

    • Modal.info({...})
    • Modal.success({...})
    • Modal.error({...})
    • Modal.warning({...})
    • Modal.confirm({...})
  1. method()是Modal的方法即先給組件Modal增長對應方法,返回一個對象;性能優化

    • 經過在method(props)方法中將其方法參數做爲組件Modal的props傳入,並render(Modal);
    • 須要返回一個對象包含{update, destroy}基本代碼以下:
['confirm','info','success','error','warning'].forEach(item => {
    // eslint-disable-next-line react/no-multi-comp
    Modal[item] = ({ ...props}) => {
        let div = document.createElement('div');
        let currentConfig = Object.assign({}, props);
        document.body.appendChild(div);
        // 使用高階組件剔除Method()調用形式不可配置的props和默認值
        const FunModal = HOCModal(Modal);
        // 關閉
        const destroy = () => {
            const unmountResult = ReactDOM.unmountComponentAtNode(div);
            if (unmountResult && div.parentNode) {
                div.parentNode.removeChild(div);
            }
        }
        const render = (config) => {
            //name傳入調用的方法名,用於區分使用不一樣footer和Icon
            ReactDOM.render(<FunModal destroy={destroy} name={item} {...config} />, div);
        }
        // 更新
        const update = (newConfig) => {
            currentConfig = Object.assign({}, currentConfig,newConfig);
            render(currentConfig);
        }
        render(currentConfig);
        return {
            destroy: destroy,
            update: update
        }
    }
});
  1. 由於Modal.method()調用形式可以使用的配置props與<Modal></Modal>中的配置項和默認值有所不一樣;
  2. Modal.confirm({})中不可配置footer;Modal.info({})footer底部默認應該爲一個button,且默認值爲我知道了
  3. 再如Modal.method()不須要傳遞visible,而<Modal></Modal>形式須要傳入;
  4. 再好比Modal.method()中沒有children,而使用content做爲內容的傳遞,因此須要適配下;
  • 因此這裏考慮使用一個高階組件HocModal對傳給Modal的props進行部分剔除和默認值修改
const HOCModal = (Component) => {
    //剔除出visible,footer,closable,使其不可配,賦予永久默認值
    return ({
        visible,
        footer,
        closable,
        okText='知道了',
        okType='primary',
        onOk=() => {},
        onCancel=() => {},
        maskClosable= false,
        content='Basic body',
        name,
        destroy,
        ...props
    }) => {
        // 修改onOk方法傳入關閉Modal方法destroy();
        const onOk_1 = () => {
            onOk();
            destroy();
        }
        // 修改onCancel方法傳入關閉Modal方法destroy();
        const onCancel_1 = () => {
            onCancel();
            destroy();
        }
        // Modal底部footer固定使用這裏爲默認值,且不可自定義footer,若是調用的是confirm返回undefined走Modal的默認配置,其餘則只顯示一個OK、button
        // eslint-disable-next-line react/no-multi-comp
        const Footer = () => (
            name == 'confirm' ? undefined : <Button onClick={onOk_1} type={okType}>{okText}</Button>
        )
        return (
        <Component
            okText={okText}
            closable={false}
            maskClosable={maskClosable}
            onOk={onOk_1}
            footer={Footer()}
            onCancel={onCancel_1}
            children={content}
            okType={okType}
            visible
            {...props}
        />
    )
    }
}
  • 使用測試
const ModalConfirm = () => {
    const onInfo = () => {
        Modal.info({
        title: 'Info',
        content: (
          <div>
            <p>some messages...some messages...</p>
            <p>some messages...some messages...</p>
          </div>
        ),
        onOk() {}
      });
    }
    const showDeleteConfirm = () => {
        const modal = Modal.confirm({
          title: '你肯定須要刪除該項麼?',
          content: '一些刪除提示內容',
          okText: '刪除',
          okType: 'danger',
          cancelText: '取消',
          onOk() {
            console.log('OK');
          },
          onCancel() {
            console.log('Cancel');
          }
        });
        console.log(modal);
    }
    return (
        <div>
            <Button onClick={showDeleteConfirm} type="dashed">刪除</Button>
            <Button  onClick={onInfo} type="primary">info</Button>
        </div>
    )
}
  • 結果展現

clipboard.png

clipboard.png

其餘優化

  1. 顯隱的動畫過渡;
  2. 組件的保留,這裏只實現了關閉即摧毀;優化爲可選擇不摧毀只是隱藏;
  3. 支持異步加載關閉
「積跬步、行千里」—— 持續更新中~,喜歡的話留下個贊和關注哦!
  • 下期考慮Carousel走馬燈封裝
相關文章
相關標籤/搜索