命令式与声明式弹窗
常用antd或者类似组件库的同学对Modal组件一定不陌生,类似antd的Modal,我之前一位师兄称之为声明式弹窗,意思是需要声明一个变量visible,控制弹窗的显示隐藏,且这个变量由调用出维护,性能上有些问题,写起来也非常的麻烦,一旦页面调用了多个Modal,代码量就会变得非常大,并且会有多个变量仅仅是控制Modal的显示隐藏。 但是在前端领域里,有一些其他的组件,例如antd的message,是一种命令式的调用方式-调用即渲染,即用即调。可以省去控制的变量visivble,可以将model部分的代码独立成模块。看一下下面的例子:
这是我在antd官网拿的一个demo
可以看到有维护变量,且代码都在同一文件
import { Modal, Button } from 'antd';
class App extends React.Component {
state = { visible: false };
showModal = () => {
this.setState({
visible: true,
});
};
handleOk = e => {
console.log(e);
this.setState({
visible: false,
});
};
handleCancel = e => {
console.log(e);
this.setState({
visible: false,
});
};
render() {
return (
<>
<Button type="primary" onClick={this.showModal}>
Open Modal
</Button>
<Modal
title="Basic Modal"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</>
);
}
}
这是我实现的一个命令式弹窗
可以看到主文件的代码量变得非常简洁,并且逻辑变得非常清晰。close事件在model内部集成了。这里可以穿入onclose事件,做关闭时的操作
import { Button } from 'antd';
import Modal from './Model.tsx'
class App extends React.Component {
state = { visible: false };
handleOk = e => {
console.log(e);
};
onClose = () => {
console.log('close')
}
showModal = () => {
Modal.show({
onOk: this.handleOk,
onClose: this.onClose
})
}
render() {
return (
<>
<Button type="primary" onClick={this.showModal}>
Open Modal
</Button>
</>
);
}
}
命令式弹窗如何实现呢
实现方式有很多,我这里采用的是高阶函数和高阶组件,这两者原理都是一样,且不仅仅可以用来做Model 的命令化,可以使用与任何其他组件。
原理:通过高阶函数对原组件添加show,hide方法,实现内部控制modal的 渲染和卸载
这是我同事急于antdmodal做的一个定制modal,我在这里定义了wrapper高阶函数,为其赋于show, hide能力
可以看到实现也并不复杂,wrapper,函数内部自动生成并向body注入一个container,将组件渲染其上,卸载方法hide,也被传入内部组件,且自动调用
show方法作为调用方法,这里接受props,直接透传,给使用者更好的使用体验,使用方式如上面的例子。
import React from 'react'
import ReactDOM from 'react-dom'
import { Modal } from 'antd'
import { CloseOutlined } from '@ant-design/icons'
import { ModalProps } from 'antd/lib/modal'
import { CSS_MODAL_PREFIX } from './contant'
import ModalFooter from './ModalBottom'
import cls from 'classcombine'
import Styles from './index.less'
interface MyProps extends ModalProps {
footer?: any
okText?: string
cancelText?: string
onOk?: () => any
onCancel?: () => any
className?: string
confirmLoading?: boolean
children?: JSX.Element
hide(): void
}
...
return (
<Modal
className={cls({
[Styles[`${CSS_MODAL_PREFIX}-content`]]: true,
[className]: !!className,
})}
closeIcon={renderCloseIcon()}
footer={footer || renderFooter()}
destroyOnClose
{...props}
/>
)
}
function Wrapper(Component: React.FC<MyProps>) {
const container = document.createElement('div')
const hide = function () {
ReactDOM.unmountComponentAtNode(container)
document.body.removeChild(container)
}
const show = function (props: MyProps) {
document.body.appendChild(container)
ReactDOM.render(<Component {...props} hide={hide} />, container)
}
return {
show,
}
}
export default Wrapper(ExhibitorModal)
最后
相比于声明式,命令式在使用的便利性,与代码的优雅程度,性能方面都有一定的提升,但如同其他modal,遇到路由的时候这里还是会报错,到时候可以用withroute包裹一下。遇到models之类的redux库,目前还没有测试,但状态管理的问题可以通过回调解决,也可以通过connect解决。