共计 5113 个字符,预计需要花费 13 分钟才能阅读完成。
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 走马灯封装