React modal自定义 openModal

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: '...'
})
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值