注册

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

前言


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


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


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

0 个评论

要回复文章请先登录注册