我们是如何封装项目里的共用弹框的
前言
随着产品的迭代,项目里的弹框越来越多,业务模块共用的弹框也比较多。在刚开始的阶段,有可能不是共用的业务弹框,我们只放到了当前的业务模块里。随着迭代升级,有些模块会成为通用弹框。简而言之,一个弹框会在多个页面中使用。举例说下我们的场景。
项目当中有这样一个预览的弹框,已经存放在我们的业务组件当中。内容如下
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 = () => {
porps.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 = (porps) => {
// 显示弹框
const showPreview = () => {
porps.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.porps.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} />
</ZetModal>
</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 = (porps) => {
// 显示弹框
const showPreview = (params) => {
Preview.show()
}
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(params)
}
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(params)
}
return (<div>
<Button onClick={showPreview}>预览</Button>
</div>)
}
export default A
结束语
对全局弹框做的统一处理,大家有问题,评论一起交流。
链接:https://juejin.cn/post/6989158134530965512