命令式弹窗--更优雅的Modal使用方式

7,402 阅读3分钟

命令式与声明式弹窗

​ 常用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.handleOkonClose: 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解决。