闽公网安备 35020302035485号
<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,可取代 registerimport 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>
);
});
最后