之前的需求有一些弹窗,弹窗内容比较复杂,在 React 项目中怎么方便高效地实现一个业务弹窗组件呢?经历了一段时间的演变,终于找到了比较方便的方法……

下面以简单的设置文案的弹窗为例列举一下弹窗的实现。

使用的基础弹窗组件是 antd modal

一般写法

比较常见的写法就是通过标签实现,也是我最开始常用的,但总觉得不爽。

🎉 完整 demo

弹窗组件的实现:

class DialogCustom extends React.Component {
 constructor(props) {
   super(props);
   this.state = { text: '' };
 }

 handleOk = () => {
   this.props.onOk(this.state.text);
   this.props.onClose();
 }

 onChange = (e) => {
   this.setState({ text: e.target.value });
 }

 render() {
   const { visible, onClose } = this.props;

   return (
     <Modal title="设置文案" visible={visible} onOk={this.handleOk} onCancel={onClose}>
       <Input value={this.state.text} onChange={this.onChange} />
     </Modal>
   );
 }
}

使用组件:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      visible: false, // 要增加一个 visible 很麻烦有木有
      text: ''
    };
  }

  handleOk = (v) => {
    this.setState({ text: v });
  }

  showDialog = () => {
    this.setState({ visible: true });
  }

  handleClose = () => {
    this.setState({ visible: false });
  }

  render() { // 只能通过标签调用
    return (<div>
      <Button onClick={this.showDialog}>设置文案</Button>
      <DialogCustom visible={this.state.visible} onOk={this.handleOk} onClose={this.handleClose} />
      <div>{this.state.text}</div>
    </div>);
  }
}

本方法的麻烦之处在于:

  • 实现一个弹窗组件时每次都要引入 Modal 组件;

  • 使用组件时,只能用标签的方式,且需要有一个 visible state 控制弹窗状态;

这样写一个其实也没什么,多了以后就烦了……

快捷调用

针对上面使用组件的麻烦,希望使用组件时可以通过 xxx.show() 的方式快捷调用,不需要标签,不需要通过 visible state 控制。

解决:给组件加上 show 方法。跟上一方法的差别就是增加了一个 show 方法,另外 <Modal />visible 属性直接设为 true 即可

🎉 完整 demo

弹窗组件的实现:

class DialogCustom extends React.Component {
 static show = params => {
   let container = document.createElement("div");
   document.body.appendChild(container);

   function closeHandle() {
     ReactDOM.unmountComponentAtNode(container);
     document.body.removeChild(container);
     container = null;
   }

   ReactDOM.render(<DialogCustom {...params} onClose={closeHandle} />, container);
 };

 ...

 render() {
   return (<Modal  ... visible={true}>...</Modal>);
 }
}

使用组件:

DialogCustom.show({ onOk: this.handleOk }); // 调用 show 想弹就弹,弹得响亮;直接可关闭,不用再增加 visible state 去控制

本方法的麻烦之处在于:

  • 实现一个弹窗组件时每次都要引入 Modal,都要重写一次 show 方法

这样写几个,多了以后还是烦了……

高阶组件

针对 “实现一个弹窗组件时每次都要引入 Modal,都要重写一次 show 方法” 的问题,可以用高阶组件解决

🎉 完整 demo

高阶组件:

const withDialog = WrappedComponent => {
 function EnhancedComponent(props) {
   const { title, onClose, ...others } = props;
   return (<Modal visible={true} title={title || WrappedComponent.title} footer={<div />}>
       <WrappedComponent {...others} onClose={onClose} />
   </Modal>);
 }

 EnhancedComponent.show = params => {
   let container = document.createElement("div");
   document.body.appendChild(container);

   function closeHandle() {
     ReactDOM.unmountComponentAtNode(container);
     document.body.removeChild(container);
     container = null;
   }

   ReactDOM.render(<EnhancedComponent {...params} onClose={closeHandle} />, container);
 };

 return EnhancedComponent;
};

设置文案的组件几乎只要处理本身的逻辑,跟弹窗相关的有两个点:

  • 高阶组件赐予了业务组件一个 onClose 的属性以拥有关闭弹窗的权利,业务组件要注意下关闭弹窗的时机。
  • footer 就由业务组件全权承包了……

如果不需要弹窗了,那么不使用高阶组件,直接使用 SetText 即可(此处应该要再处理下 footer)。

业务组件:

@withDialog // 使用高阶组件,很关键
class SetText extends React.Component { // 只要处理本身的逻辑,几乎不用在意弹窗
 static title = "设置文案";

 static defaultProps = {
   onClose: () => {}
 };

 constructor(props) {
   super(props);
   this.state = { text: "" };
 }

 onChange = e => {
   this.setState({ text: e.target.value });
 };

 handleOk = () => {
   this.props.onOk(this.state.text);
   this.props.onClose();
 };

 render() {
   return (<div>
     <Input value={this.state.text} onChange={this.onChange} />
     <div>
       <Button onClick={this.handleOk}>确定</Button>
       <Button onClick={this.props.onClose}>取消</Button>
     </div>
   </div>);
 }
}

使用组件:

SetText.show({ onOk: this.handleOk });

本方法的问题在于:

  • footer 一定需要自定义,注意保持样式统一

通过这种方式,几乎不用侵入你的组件,你的组件就可以披上一个弹窗。

注:文本案例基于 antd modal 实现,但不限于 antd modal,你可以自己封装一个基础弹窗就可以实现弹弹弹。