modal.tsx
/* eslint-disable */
import React from 'react';
import { Modal } from 'antd';
import { ModalProps } from 'antd/lib/modal';
import { soloMount, MountWarpProps } from 'utils/solo-mount/mount';
import { isFunction } from '@kirin/mall-utils';
export interface ModalShellProps {
visible: boolean;
loading: boolean;
toggleLoading(loading: boolean): void;
destroy(delay?: number): void;
hide(): void;
}
function shellModal<T extends ModalShellProps>(ModalComponent: React.ComponentType<T>) {
return class ModalShell extends React.Component<T, { visible: boolean; loading: boolean }> {
state = { visible: true, loading: false };
hide = () => this.setState({ visible: false });
toggleLoading = (loading = false) => this.setState({ loading });
render() {
return (
<ModalComponent
{...this.props}
// destroyOnClose
toggleLoading={this.toggleLoading}
visible={this.state.visible}
loading={this.state.loading}
hide={this.hide}
/>
);
}
};
}
let mountPoint: MountPointProvider = null;
type Invader<T> = {
Component: React.ComponentType<T & MountWarpProps>;
props: T;
};
export class MountPointProvider extends React.PureComponent<any, { invaders: React.ReactNode[]; mounted: boolean }> {
static id = 1;
originInvaderIds = [];
constructor(props) {
super(props);
mountPoint = this;
this.state = {
invaders: [],
mounted: false,
};
}
// 挂载
addMount<T>(invader: Invader<T>) {
const __id = MountPointProvider.id++;
const destroy = () => this.destroy(__id);
const ref: React.RefObject<T> = React.createRef();
this.originInvaderIds = [...this.originInvaderIds, __id];
const invaders = [
...this.state.invaders, // 在setState中引用之前的状态时
<invader.Component key={__id} {...invader.props} destroy={destroy} ref={ref} />,
];
this.setState({ invaders });
return { destroy, ref };
}
// 移除
destroy(id: number) {
const { invaders } = this.state;
let position;
this.originInvaderIds = this.originInvaderIds.filter((current, index) => {
if (id === current) {
position = index;
return false;
}
return true;
});
this.setState({
invaders: invaders.filter((invader, index) => {
return index !== position;
}),
});
}
componentDidMount() {
this.setState({ mounted: true });
}
componentWillUnmount() {
mountPoint = null;
}
render() {
const { mounted, invaders } = this.state;
return mounted ? invaders : null;
}
}
export function soloModal<T>(Component: React.ComponentType<T & ModalShellProps>, props: T) {
if (mountPoint) {
return mountPoint.addMount({ Component: shellModal(Component), props });
}
return soloMount(shellModal(Component), props);
}
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
export type WrapProps = Omit<
ModalProps,
'visible' | 'afterClose' | 'onOk' | 'onCancel' | 'isConfirm' | 'handleCloseModal'
> & {
content: React.ReactNode | string;
onOk: () => void | Promise<any>;
onCancel: () => void | Promise<any>;
handleCloseModal: () => void | Promise<any>;
isConfirm: false; // 是否需要二次确认
};
function FreeModal(props: WrapProps & ModalShellProps) {
const { destroy, hide, onOk, onCancel, toggleLoading, loading, isConfirm, handleCloseModal, content, ...config } =
props;
const modalProps: ModalProps = Object.assign(config, {
onCancel: hide,
onOk: hide,
afterClose: destroy,
confirmLoading: loading,
});
if (onOk) {
modalProps.onOk = async () => {
toggleLoading(true);
try {
await onOk();
hide();
} catch (e) {
toggleLoading(false);
}
};
}
if (onCancel) {
modalProps.onCancel = async () => {
if (isFunction(onCancel)) {
try {
toggleLoading(true);
await onCancel();
} catch (e) {} // 空的代码块
}
!isConfirm ? hide() : handleCloseModal(hide);
};
}
return (
<Modal {...modalProps} footer={props.footer}>
{content}
</Modal>
);
}
export function openModal(props: WrapProps & ModalShellProps) {
const { destroy, ref } = soloModal(FreeModal, props);
return { destroy, hide: () => ref.current && ref.current.hide() };
}
/* eslint-enable */
mount.tsx
import * as ReactDOM from 'react-dom';
import * as React from 'react';
export interface MountWarpProps {
destroy(): void;
}
export interface ReturnHooks<T> {
destroy(): void;
ref: React.RefObject<T>;
}
export function soloMount<T>(Component: React.ComponentType<T & MountWarpProps>, props: T): ReturnHooks<T> {
// 创建一个div并放入document中用于挂载
const div = document.createElement('div');
document.body.appendChild(div);
const ref: React.RefObject<T> = React.createRef();
// 卸载并移除div
function destroy() {
const unmountResult = ReactDOM.unmountComponentAtNode(div);
if (unmountResult && div.parentNode) {
div.parentNode.removeChild(div);
}
}
ReactDOM.render(<Component destroy={destroy} ref={ref} {...props} />, div);
return { destroy, ref };
}
使用
import { openModal } from 'utils/solo-mount';
openModal({
title:'modal标题',
content: '...',
footer: '...'
})