两种调用方式:

  1. 创建时传入props
 const C = injectModal(Component, props);
 C.open(); 
  1. 调用时传入props
const C = injectModal(Component, {});
C.open(props);
import type { CSSProperties } from 'react';
 
import ReactDom from 'react-dom';
import QueueAnim from 'rc-queue-anim';
 
type ElementProps<T> = T extends React.ComponentType<infer Props> ? (Props extends object ? Props : never) : never;
type AnimationType = ElementProps<typeof QueueAnim>;
 
interface ComStatic<Props> {
  open: (params?: Props) => void;
  close: () => void;
}
 
const defaultStyles: CSSProperties = {
  position: 'absolute',
  zIndex: 1000,
  left: 0,
  top: 0,
  right: 0,
  bottom: 0,
  margin: 'auto',
};
 
/**
 * @param Component
 * @param injector
 * @param animation QueueAnim 动画
 */
let mount: HTMLDivElement | null;
function injectModal<TProps, TInjectedKeys extends keyof TProps>(params: {
  Component: React.JSXElementConstructor<TProps>;
  injector: Pick<TProps, TInjectedKeys>;
  animation?: AnimationType;
  styles?: CSSProperties;
}) {
  const { Component, injector, animation, styles } = params;
  const C: typeof Component & ComStatic<Omit<TProps, TInjectedKeys>> = Component as typeof Component &
    ComStatic<Omit<TProps, TInjectedKeys>>;
 
  C.open = (props?: Omit<TProps, TInjectedKeys>) => {
    if (mount) {
      return;
    }
 
    mount = document.createElement('div');
    document.body.appendChild(mount);
 
    const key = FE.getRandomString(true, 100, 400);
 
    ReactDom.render(
      <QueueAnim animConfig={{ opacity: [1, 0], scale: [1, 0.5] }} duration={500} forcedReplay {...animation}>
        <div key={key} style={{ ...defaultStyles, ...styles }}>
          {C ? <C {...(props as TProps)} {...injector} /> : null}
        </div>
      </QueueAnim>,
      mount,
    );
  };
 
  C.close = () => {
    if (mount) {
      ReactDom.unmountComponentAtNode(mount);
      document.body.removeChild(mount);
      mount = null;
    }
  };
 
  return C;
}
 
export default injectModal;