Madal组件实现基本简介
- 类似于antd实现的modal组件,首先基本结构分析:
-
modal-mask
遮罩层 -
modal-warp
内容包装层 -
modal
主体内容层,包含:title
、content
、footer
、close-btn
- 固定定位布局,全屏遮盖显示,所以内容自定义
-
- 实现功能目标:
- 两种调用方式
<Modal {...props}>一些内容</Modal>
、Modal.confirm({...props})
- 遮罩层、
footer
和close-btn
的显示与否,单击是否可关闭 - 其他必备功能
- 两种调用方式
结构布局攻克
- 基本布局
<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>
- 遮罩层全屏覆盖
-
position: fixed
定位 - 全屏实现
top: 0; right: 0; bottom: 0; left: 0; z-index: 1000;
-
- 内容层
-
position: fixed
定位(modal-warp层) warp层的布局大小考虑
- 全屏:如果warp层实现全屏,由于和mask层为兄弟组件,导致warp层位于mask层之上,后面对mask层单击可关闭功能易出现单击不到,因为被全屏的warp层遮挡(可考虑使用事件委托,将单击事件绑定至第一个父组件,通过判断去除
modal
层的单击,虽然单击的还是warp层); - 大小跟随
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>
)
}
}
- 效果
升级篇Modal.method()
的攻克
- 如何实现类似
antd
中modal.method的方法调用弹窗形式(且调用后返回一个引用包含{update, destroy}
可控制弹窗):Modal.info({...})
Modal.success({...})
Modal.error({...})
Modal.warning({...})
Modal.confirm({...})
-
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
}
}
});
- 因为
Modal.method()
调用形式可使用的配置props与<Modal></Modal>
中的配置项和默认值有所不同; - 如
Modal.confirm({})
中不可配置footer
;Modal.info({})
的footer
底部默认应该为一个button
,且默认值为我知道了
; - 再如
Modal.method()
不需要传递visible
,而<Modal></Modal>
形式需要传入; - 再比如
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>
)
}
- 结果展示
其他优化
- 显隐的动画过渡;
- 组件的保留,这里只实现了关闭即摧毁;优化为可选择不摧毁只是隐藏;
- 支持异步加载关闭
“积跬步、行千里”—— 持续更新中~,喜欢的话留下个赞和关注哦!
- 往期经典好文:
- 你不知道的CORS跨域资源共享
- 性能优化篇---Webpack构建速度优化
- 团队合作必备的Git操作
- 使用pm2部署node生产环境
- 下期考虑Carousel走马灯封装