<Modal visible={visible} onClose={onClose} />现在我们需要给它添加一个指令式调用的方法:show,可以通过 Modal.show() 来弹出这个弹框,那么我们该如何实现呢?首先的想法是:我们写一个 renderToBody 的方法,调用 show 的时候,其实就是把 Modal 渲染到 body 上:
function renderToBody(element) { const container = document.createElement("div"); document.body.appendChild(container); const root = createRoot(container); root.render(element); function unmount() { root.unmount(); if (container.parentNode) { container.parentNode.removeChild(container); } } return unmount; }然后实现 Modal.show 方法:
Modal.show = (props) => { const unmount = renderToBody( <Modal {...props} visible onClose={() => { props.onClose(); unmount(); }} /> ); return unmount; };至此,我们实现了一个简单版本的 Modal.show,用起来,感觉也没啥没问题,直到我们想给 Modal 加上入场和退出动画,并且 Modal 内部的动画是基于 js(比如基于react-spring 库)实现的:
Modal.show = (props) => { const Wrapper = forwardRef((_, ref) => { const [visible, setVisible] = useState(false); useEffect(() => { setVisible(true); }, []); function handleClose() { props.onClose?.(); setVisible(false); } useImperativeHandle(ref, () => ({ close: handleClose, })); function handleAfterClose() { props.afterClose?.(); unmount(); } return ( <Modal {...props} visible={visible} onClose={handleClose} afterClose={handleAfterClose} /> ); }); const ref = createRef(); const unmount = renderToBody(<Wrapper ref={ref} />); const close = () => { ref.current?.close(); }; return { close, }; };上面的代码主要做了三件事:
在 onClose 中,把 visible 设置为 false,这样就可以触发 Modal 的退出动画了,在 handleAfterClose 中,执行 unmount,这样就可以在退出动画结束后,执行 unmount 了。
通过 forwardRef + useImperativeHandle,把 Wrapper 组件的 close 方法暴露出去,这样我们就可以在外部调用 Modal.show() 返回的对象的 close 方法,来关闭 Modal 了。
# with yarn yarn add @ebay/nice-modal-react # or with npm npm install @ebay/nice-modal-react创建自己的组件
import { Modal } from "antd"; import NiceModal, { useModal } from "@ebay/nice-modal-react"; export default NiceModal.create(({ name }) => { // Use a hook to manage the modal state const modal = useModal(); return ( <Modal title="Hello Antd" onOk={() => modal.hide()} visible={modal.visible} onCancel={() => modal.hide()} afterClose={() => modal.remove()} > Hello {name}! </Modal> ); });从代码中我们可以看到:
import NiceModal from "@ebay/nice-modal-react"; ReactDOM.render( <React.StrictMode> <NiceModal.Provider> <App /> </NiceModal.Provider> </React.StrictMode>, document.getElementById("root") );如果使用 Next.js 的 app route,可以在 layout 中放置 NiceModal.Provider
// app/layout.tsx export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en"> <body className={poppins.className}> <main> <NiceModalProvider>{children}</NiceModalProvider> </main> </body> </html> ); }在任何组件内使用 NiceModal.create 创建的组件
import NiceModal from "@ebay/nice-modal-react"; import MyAntdModal from "./my-antd-modal"; // created by above code function App() { const showAntdModal = () => { // Show a modal with arguments passed to the component as props NiceModal.show(MyAntdModal, { name: "Nate" }); }; return ( <div className="app"> <h1>Nice Modal Examples</h1> <div className="demo-buttons"> <button onClick={showAntdModal}>Antd Modal</button> </div> </div> ); }或者给你的 Modal 组件注册一个 id,然后使用这个 id 来调用:
import NiceModal from "@ebay/nice-modal-react"; import MyAntdModal from "./my-antd-modal"; // created by above code // If use by id, need to register the modal component. // Normally you create a modals.js file in your project // and register all modals there. NiceModal.register("my-antd-modal", MyAntdModal); function App() { const showAntdModal = () => { // Show a modal with arguments passed to the component as props NiceModal.show("my-antd-modal", { name: "Nate" }); }; return ( <div className="app"> <h1>Nice Modal Examples</h1> <div className="demo-buttons"> <button onClick={showAntdModal}>Antd Modal</button> </div> </div> ); }我们通常会把项目内所有 自定义 Modal 的注册放到一个单独的文件中,比如 modals.js,然后在项目的根组件中,引入这个文件,这样就可以在项目的任何地方,通过 id 来调用 Modal 了。这种使用方式,可以解耦 Modal 组件和调用方。
import NiceModal, { useModal } from "@ebay/nice-modal-react"; import MyAntdModal from "./my-antd-modal"; // created by above code NiceModal.register("my-antd-modal", MyAntdModal); //... // if use with id, need to register it first const modal = useModal("my-antd-modal"); // or if with component, no need to register const modal = useModal(MyAntdModal); //... modal.show({ name: "Nate" }); // show the modal modal.hide(); // hide the modal //...声明式的使用 Modal,可取代 register
import NiceModal, { useModal } from "@ebay/nice-modal-react"; import MyAntdModal from "./my-antd-modal"; // created by above code function App() { const showAntdModal = () => { // Show a modal with arguments passed to the component as props NiceModal.show("my-antd-modal"); }; return ( <div className="app"> <h1>Nice Modal Examples</h1> <div className="demo-buttons"> <button onClick={showAntdModal}>Antd Modal</button> </div> <MyAntdModal id="my-antd-modal" name="Nate" /> </div> ); }这种使用方式,你可以享受以下便利:
2.可以通过 props 传递参数
NiceModal.show(UserAgeModal) .then((age) => { // 根据用户的年龄,执行不同的逻辑 }) .catch((err) => { // 用户取消了 });UserAgeModal 组件的实现如下:
const PromiseModal = NiceModal.create(() => { const modal = useModal(); const [age, setAge] = useState(0); const [error, setError] = useState(null); const handleOk = () => { modal.resolve(age); mode.hide(); }; const handleCancel = () => { modal.reject(); mode.hide(); }; return ( <Modal title="请输入您的年龄" visible={modal.visible} onOk={handleOk} onCancel={handleCancel} > <InputNumber value={age} onChange={(value) => { setAge(value); }} /> </Modal> ); });最后