32

Madal组件实现基本简介

clipboard.png

  • 类似于antd实现的modal组件,首先基本结构分析:

    1. modal-mask遮罩层
    2. modal-warp内容包装层
    3. modal主体内容层,包含:titlecontentfooterclose-btn
    • 固定定位布局,全屏遮盖显示,所以内容自定义
  • 实现功能目标:

    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. 内容层

    • 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走马灯封装

keywords
4.8k 声望2.1k 粉丝

新篇章,起航!