背景
最近在和同事合作一个需求,其中很多模块都需要一个相同风格的简单弹窗组件,而项目中引入的antd-mobile无法直接满足需求,最后决定由我来做一次二次封装。
开始
一开始我的理解是封装一层样式,新增一些特有的属性,其他的props透传就完事了,于是有了下面的组件
CommonModal
import React from 'react'
import Modal, { ModalProps } from 'antd-mobile'
interface IProps extends ModalProps {
content: string
commonModalClassName?: string
}
const CommonModal: React.FC<IProps> = (props) => {
const { commonModalClassName = '', content = '', ...resProps} = props
return (
<div className={`common-modal ${ commonModalClassName }`}>
<Modal {...resProps}>
<p className="common-modal-content">{ content }</p>
</Modal>
</div>
)
}
看起来简单粗暴没毛病。
使用Hook优化
功能已经实现,但同事用的时候反馈,作为一个简单的弹窗,每次用的时候还需要定义好state,还要引入CommonModal组件, 比较麻烦
const [visible, setVisible] = useState(false)
const [content, setContent] = useState(false
...
这时我想起来antd-mobile的Modal可以直接调用Modal.alert来唤起弹窗,对于多处复用的弹窗,像这样封装一个可执行的方法是不错的。先看看antd-mobile的实现。
export default function alert(){
const div: any = document.createElement('div');
document.body.appendChild(div);
function close() {
ReactDOM.unmountComponentAtNode(div);
if (div && div.parentNode) {
div.parentNode.removeChild(div);
}
}
ReactDOM.render(
<Modal
visible
transparent
title={title}
transitionName="am-zoom"
closable={false}
maskClosable={false}
footer={footer}
maskTransitionName="am-fade"
platform={platform}
wrapProps={{ onTouchStart: onWrapTouchStart }}
>
<div className={`${prefixCls}-alert-content`}>{message}</div>
</Modal>,
div,
);
}
贴了部分核心代码,其实就是用ReactDOM来渲染弹窗,用dom.parentNode.removeChild来卸载弹窗。
用Hook实现
useCommonModal
import React, { useCallback, useRef } from 'react'
import ReactDOM from 'react-dom'
import CommonModal, { IProps as ModalProps } from './index'
const Modal: React.FC<ModalProps> = React.memo(({ visible, ...res }) => <CommonModal visible={ visible } { ...res } />)
type IProps = Pick<ModalProps, 'mainText' | 'subText' | 'btnText' | 'type' | 'onClose'>
const useCommonModal = (
props: IProps,
): {
closeModal: () => void
showModal: () => void
} => {
const domRef = useRef(null)
/**
* 关闭弹窗
*/
const closeModal = useCallback(() => {
const dom = domRef.current
ReactDOM.unmountComponentAtNode(dom)
if (dom && dom.parentNode) {
dom.parentNode.removeChild(dom)
}
const { onClose } = props
if (typeof onClose === 'function') {
onClose()
}
}, [props])
/**
* 展示弹窗
*/
const showModal = useCallback(() => {
const Root = document.body
if (!domRef.current) {
// 创建一个dom
domRef.current = document.createElement('div')
Root.appendChild(domRef.current)
}
const dom = domRef.current
const ele = React.createElement(
Modal,
{
visible: true,
onClose: closeModal,
onClickBtn: closeModal,
maskClosable: true,
...props,
},
null,
)
ReactDOM.render(ele, dom)
}, [closeModal, props])
return {
closeModal,
showModal,
}
}
export default useCommonModal
使用
const { show, close } = useCommonModal({
title: '',
content: '',
btnText: '',
})