两种调用方式: 创建时传入props const C = injectModal(Component, props); C.open(); 调用时传入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;