这样设计弹框,有更多时间来摸鱼了!
大家好,我是多喝热水。
不知道大家平时在业务中都是怎么去写弹框逻辑的,今天和大家聊一聊如何在业务中优雅的使用弹框~
假设现在有一个场景:网站里有个评论区,这个评论区需要登录后才能使用,如果用户没登录的情况下点击了这个按钮,那么我们需要弹出一个登录框让用户先登录,这是一个非常常见的需求吧,如果是你的话你会怎么做呢?
方式一
我们先用最简单的方式来实现,假设我们在业务中使用的是 Ant Design,那么我们写出来的代码应该是这样的:
▼jsx复制代码export default function LoginTrigger(props: LoginTriggerProps) { const [isModalOpen, setIsModalOpen] = useState(false); const showModal = () => { setIsModalOpen(true); }; const handleOk = () => { setIsModalOpen(false); }; const handleCancel = () => { setIsModalOpen(false); }; return ( <> <Button type="primary" onClick={showModal}> 登录 </Button> <Modal title="Basic Modal" open={isModalOpen} onOk={handleOk} onCancel={handleCancel} > 登录框内容,此处省略输入框.... </Modal> 此处省略一大坨业务代码... </> ); }
ok,我们来看一下效果如何:
和我们预想的一样,点击登录按钮,它确实给我们弹出了登录框。但是我们得思考一下,这样做有什么弊端?
1)如果我们不止评论区有这个登录的需求,像发文章、私信、甚至是有些页面加载完毕后就需要弹登录,那像这样每一个场景我们都需要再写一遍这样的代码吗?
2)不利于代码的维护,因为我们业务中可能不仅仅有登录弹框,也可能有其他弹框逻辑,这么多 Modal 混合在一起,维护起来需要耗费大量的精力,效率极低。
所以,为了我们以后能有更多时间去摸鱼,我们必须得把这些问题都消灭!
现在问题已经抛出来了,怎么解决?有点经验的小伙伴可能会说,这些弹框大概率都是重复代码,我们把它封装成一个组件不就好了吗?
方式二
直接开干,我们把这个登录弹框的逻辑给封装一下,代码如下:
▼jsx复制代码export default function useLoginModal() { const [isModalOpen, setIsModalOpen] = useState<boolean>(false); const showLoginModal = () => { setIsModalOpen(true); }; const closeLoginModal = () => { setIsModalOpen(false); }; const LoginModal = useCallback(() => { if (!isModalOpen) return <></>; return ReactDOM.createPortal( <Modal open={isModalOpen} onCancel={closeLoginModal} > 登录框内容,此处省略输入框.... </Modal>, document.body ); }, [isModalOpen]); return { showLoginModal, LoginModal }; }
然后我们后续使用这个登录弹框只需要调用 useLoginModal 函数即可,如下:
▼jsx复制代码export default function LoginTrigger(props: LoginTriggerProps) { const { LoginModal, showLoginModal } = useLoginModal(); return ( <> <Button type="primary" onClick={showLoginModal}> 登录 </Button> <LoginModal /> 此处省略一大坨业务代码... </> ); }
ok,我们再看看效果:
nice,依然是一样的效果,但是,怎么感觉还是怪怪的。
整个流程需要调用 useLoginModal 函数,又要使用一下 LoginModal 组件,最后再调一下 showLoginModal 才能弹框。。。。
你干嘛!我就只想弹个框而已,你给我搞这么一堆代码。。。。
最佳实践
上面的代码只是借助封装的特性,让我们少写了一些代码,但总的流程并没有改变,所以我们理想的效果,就是调用一个函数就可以弹框,能不能做到?答案是可以的,下面我们就来实践一下,我认为的最佳方案。
1)这里我们引入一个包 @ebay/nice-modal-react,使用方式如下:
▼jsx复制代码import NiceModal from "@ebay/nice-modal-react"; import { ReactNode } from "react"; export default function GlobalProvider({ children }) { return <NiceModal.Provider>{children}</NiceModal.Provider>; }
这一步我们将整个根组件包裹在 NiceModal.Provider 中
2)编写弹框组件内容
▼jsx复制代码import NiceModal, { useModal } from "@ebay/nice-modal-react"; import { Modal } from "antd"; export default NiceModal.create((props) => { const modalInstance = useModal(); return ( <Modal title="用户登录" open={modalInstance.visible} onOk={modalInstance.hide} onCancel={modalInstance.hide} > 登录弹框最佳实践,此处省略一些输入框 </Modal> ); });
这一步,我们使用 NiceModal 创建了一个登录弹框
3)使用弹框
▼jsx复制代码import loginModal from "@/components/globalModal/login"; import NiceModal from "@ebay/nice-modal-react"; import { Button } from "antd"; export default function LoginTrigger(props) { return <Button onClick={() => NiceModal.show(loginModal)}>登录</Button>; }
loginModal 就是我们上面创建的弹框,接下来我们看看效果如何:
依旧没有任何问题,我们现在通过调用一次函数就实现了弹框,但我们依然引入了两个新组件,如果还是觉得不够优雅,我们还可以再优化一下,如下:
▼jsx复制代码import NiceModal from "@ebay/nice-modal-react"; import LoginModal from "@/components/globalModal/login"; export const showLogin = () => NiceModal.show(LoginModal);
这里我们将这段弹框逻辑封装成一个工具函数,后续我们直接调用 showLogin 即可,维护的时候也只需要维护这个工具函数。
总结
最后一种方式弹框,把我们抛出的问题都消灭了,当然这里仅仅只是以登录为例,如果有其他弹框逻辑我们只需要创建新的 xxxModal,再通过 NiceModal.show(xxxModal) 即可。
而且通过发布订阅模式,我们也可以自己实现一个 nice-modal-react,但全部写下来篇幅有点长了,感兴趣的话我们可以后面来搞一搞。
好了,这篇文章就肝到这里,如果有更好的方式大家可以探讨一下。
