前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React组件库封装初探--Modal

React组件库封装初探--Modal

作者头像
keyWords
发布2019-04-18 20:05:58
5K2
发布2019-04-18 20:05:58
举报
文章被收录于专栏:keyWordskeyWords

Madal组件实现基本简介

clipboard.png
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. 其他必备功能

结构布局攻克

  • 基本布局
代码语言:javascript
复制
<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控制.

基本功能逻辑实现

  • 基本对外接口(函数式)
代码语言:javascript
复制
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
  • 基本使用形式
代码语言:javascript
复制
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
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}基本代码如下:
代码语言:javascript
复制
['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进行部分剔除和默认值修改
代码语言:javascript
复制
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}
        />
    )
    }
}
  • 使用测试
代码语言:javascript
复制
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
clipboard.png
clipboard.png

其他优化

  1. 显隐的动画过渡;
  2. 组件的保留,这里只实现了关闭即摧毁;优化为可选择不摧毁只是隐藏;
  3. 支持异步加载关闭

“积跬步、行千里”—— 持续更新中~,喜欢的话留下个赞和关注哦!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Madal组件实现基本简介
  • 结构布局攻克
  • 基本功能逻辑实现
  • 升级篇Modal.method()的攻克
  • 其他优化
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档