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走马灯封装
发表回复