我们是如何封装项目里的共用弹框的

3,260

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

前言

随着产品的迭代,项目里的弹框越来越多,业务模块共用的弹框也比较多。在刚开始的阶段,有可能不是共用的业务弹框,我们只放到了当前的业务模块里。随着迭代升级,有些模块会成为通用弹框。简而言之,一个弹框会在多个页面中使用。举例说下我们的场景。

项目当中有这样一个预览的弹框,已经存放在我们的业务组件当中。内容如下

import React from 'react';
import {Modal} from 'antd';

const Preview = (props) => {
    const {visible, ...otherProps} = props;
    return(
        <Modal 
          visible={visible}
          {...otherProps}
          ... // 其它Props
        >
            <div>预览组件的内容</div>
        </Modal>
    )
}

这样的一个组件我们在多个业务模块当中使用,下面我们通过不同的方式来处理这种情况。

各模块引入组件

组件是共用的,我们可以在各业务模块去使用。

在模块A中使用

import React, {useState} from 'react';
import Preview from './components/preview';
import {Button} from 'antd';

const A = () => {
    const [previewState, setPreviewState] = useState({
        visible: false,
        ... // 其它props,包括弹框的props和预览需要的参数等
    });
    
    // 显示弹框
    const showPreview = () => {
        setPreviewState({
            ...previewState,
            visible: true,
        })
    }
    
    // 关闭弹框
    const hidePreview = () => {
         setPreviewState({
            ...previewState,
            visible: false,
        })
    }

    return (<div>
        <Button onClick={showPreview}>预览</Button>
        <Preview {...previewState} onCancel={hidePreview} />
    </div>)
}

export default A;

在模块B中使用

import React, {useState} from 'react';
import Preview from './components/preview';
import {Button} from 'antd';

const B = () => {
    const [previewState, setPreviewState] = useState({
        visible: false,
        ... // 其它props,包括弹框的props和预览需要的参数等
    });
    
    // 显示弹框
    const showPreview = () => {
        setPreviewState({
            ...previewState,
            visible: true,
        })
    }
    
    // 关闭弹框
    const hidePreview = () => {
         setPreviewState({
            ...previewState,
            visible: false,
        })
    }

    return (<div>
        B模块的业务逻辑
        <Button onClick={showPreview}>预览</Button>
        <Preview {...previewState} onCancel={hidePreview} />
    </div>)
}

export default B;

我们发现打开弹框和关闭弹框等这些代码基本都是一样的。如果我们的系统中有三四十个地方需要引入预览组件,那维护起来简直会要了老命,每次有调整,需要改动的地方太多了。

放到Redux中,全局管理。

通过上面我们可以看到显示很关闭的业务逻辑是重复的,我们把它放到redux中统一去管理。先改造下Preview组件

import React from 'react';
import {Modal} from 'antd';

@connect(({ preview }) => ({
  ...preview,
}))
const Preview = (props) => {
    const {visible} = props;
    
    const handleCancel = () => {
        props.dispatch({
            type: 'preview/close'
        })
    }
    
    return(
        <Modal 
          visible={visible}
          onCancel={handleCancel}
          ... // 其它Props
        >
            <div>预览组件的内容</div>
        </Modal>
    )
}

在redux中添加state管理我们的状态和处理一些参数

const initState = {
  visible: false,
};

export default {
  namespace: 'preview',
  state: initState,
  reducers: {
    open(state, { payload }) {
      return {
        ...state,
        visible: true,
      };
    },
    close(state) {
      return {
        ...state,
        visible: false,
      };
    },
  },

};

全局引入

我们想要在模块中通过dispatch去打开我们弹框,需要在加载这些模块之前就导入我们组件。我们在Layout中导入组件

import Preview from './components/preview';
const B = () => {

    return (<div>
        <Header>顶部导航</Header>
        <React.Fragment>
            // 存放我们全局弹框的地方
            <Preview />
        </React.Fragment>
    </div>)
}

export default B;

在模块A中使用

import React, {useState} from 'react';
import Preview from './components/preview';
import {Button} from 'antd';

@connect()
const A = (props) => {
    // 显示弹框
    const showPreview = () => {
       props.dispatch({
           type: 'preview/show'
           payload: { ... 预览需要的参数}
       })
    }
    return (<div>
        <Button onClick={showPreview}>预览</Button>
    </div>)
}

export default A;

在模块B中使用

import React, {useState} from 'react';
import Preview from './components/preview';
import {Button} from 'antd';

@connect()
const B = () => {
    // 显示弹框
    const showPreview = () => {
       this.props.dispatch({
           type: 'preview/show'
           payload: { ... 预览需要的参数}
       })
    }
    return (<div>
        <Button onClick={showPreview}>预览</Button>
    </div>)
}

export default B;

放到redux中去管理状态,先把弹框组件注入到我们全局当中,我们在业务调用的时候只需通过dispatch就可以操作我们的弹框。

基于插件注入到业务当中

把状态放到redux当中,我们每次都要实现redux那一套流程和在layout组件中注入我们的弹框。我们能不能不关心这些事情,直接在业务当中使用呢。

创建一个弹框的工具类

class ModalViewUtils {

  // 构造函数接收一个组件
  constructor(Component) {
    this.div = document.createElement('div');
    this.modalRef = React.createRef();
    this.Component = Component;
  }

  onCancel = () => {
    this.close();
  }

  show = ({
    title,
    ...otherProps
  }: any) => {
    const CurrComponent = this.Component;
    document.body.appendChild(this.div);
    ReactDOM.render(<GlobalRender>
        <Modal
          onCancel={this.onCancel}
          visible
          footer={null}
          fullScreen
          title={title || '预览'}
          destroyOnClose
          getContainer={false}
        >
          <CurrComponent {...otherProps} />
        </Modal>
      </GlobalRender>, this.div)

  }
  
  close = () => {
    const unmountResult = ReactDOM.unmountComponentAtNode(this.div);
    if (unmountResult && this.div.parentNode) {
      this.div.parentNode.removeChild(this.div);
    }
  }

}

export default ModalViewUtils;

更改Preview组件

import React, { FC, useState } from 'react';
import * as ReactDOM from 'react-dom';
import ModalViewUtils from '../../utils/modalView';

export interface IModalViewProps extends IViewProps {
  title?: string;
  onCancel?: () => void;
}

// view 组件的具体逻辑
const ModalView: FC<IModalViewProps> = props => {
  const { title, onCancel, ...otherProps } = props;
  return <View isModal {...otherProps} />
}

// 实例化工具类,传入对用的组件
export default new ModalViewUtils(ModalView);

在模块A中使用

import React, {useState} from 'react';
import Preview from './components/preview';
import {Button} from 'antd';

const A = (props) => {
    // 显示弹框
    const showPreview = (params) => {
       Preview.show(params)
    }
    return (<div>
        <Button onClick={showPreview}>预览</Button>
    </div>)
}

export default A;

在模块B中使用

import React, {useState} from 'react';
import Preview from './components/preview';
import {Button} from 'antd';

const B = () => {
    // 显示弹框
    const showPreview = () => {
         Preview.show()
    }
    return (<div>
        <Button onClick={showPreview}>预览</Button>
    </div>)
}

export default B;

基于这种方式,我们只用关心弹框内容的实现,调用的时候直接引入组件,调用show方法, 不会依赖redux,也不用再调用的地方实例组件,并控制显示隐藏等。

基于Umi插件,不需引入模块组件

我们可以借助umi的插件,把全局弹框统一注入到插件当中, 直接使用。

import React, {useState} from 'react';
import {ModalView} from 'umi';
import {Button} from 'antd';

const A = () => {
    // 显示弹框
    const showPreview = () => {
         ModalView.Preview.show()
    }
    return (<div>
        <Button onClick={showPreview}>预览</Button>
    </div>)
}

export default A

结束语

对全局弹框做的统一处理,大家有问题,评论一起交流。

如果你觉得该文章不错,不妨

1、点赞,让更多的人也能看到这篇内容

2、关注我,让我们成为长期关系

3、关注公众号「前端有话说」,里面已有多篇原创文章,和开发工具,欢迎各位的关注,第一时间阅读我的文章