背景
弹窗在现代应用中是最为常见的一种展示信息的形式,二次确认弹窗是其中最为经典的一种。当我们在React,Vue这种数据驱动视图的前端框架中渲染弹窗基本是固定的使用形式。
使用方式:创建新的弹窗组件,在需要弹窗的地方引用并且需要在外层维护弹窗组件的显示/隐藏状态。
这只是庞大项目中一处需要弹窗的地方,如果项目中存在N个需要弹窗的场景,我们都需要将上述步骤重复一次。这会让我们的项目组件变得臃肿冗余。这也是前端开发者比较头疼且常见的问题。那么我们有没有办法解决这种问题呢?
思考
在最近的一次需求中,我尝试去解决这个问题。首先对于二次确认弹窗这种只有在执行某些动作的时候才需要进行展示,其他时间我们应该忽视它的存在。很明显根据传统的做法我们至少需要维护一个组件实例以及一个状态。
是否可以实现只有在执行具体的动作时才调用二次确认弹窗相关的代码呢?就比如命令式调用方法去渲染组件,可以通过ReactDOM.render实现的这种效果。
接着尝试思考第二个问题,对于二次确认弹窗的交互,其实本质上我们只关心用户点击的取消还是确认。对于这种状态二选一的问题,我脑海里面浮现出熟悉的Promise。
于是,我尝试将二次确认弹窗与Promise进行有机结合,实现出命令式二次确认弹窗。当然这种思路可以运用到其他任何类似的场景。
在线演示Live Demo
事实胜于雄辩,这是下方代码实现的真实效果,感兴趣的同学可自行访问看看效果。
Reconfirm Modal DEMO - FE Component Training
代码实现
普通方式实现
-
先通过通用方式实现二次确认弹窗
import { ReactNode } from "react"; import { Button } from "antd"; import "./index.css"; interface ModalOption { title: ReactNode; content: ReactNode; okText?: string; cancelText?: string; } interface ReconfirmModalProps { onConfirm: () => void; onClose: () => void; options: ModalOption; } function ReconfirmModal(props: ReconfirmModalProps) { const { onConfirm, onClose, options } = props; const { title, content, okText, cancelText } = options; return ( <div className="reconfirm-modal" style={{ zIndex: 10001 }}> <div className="reconfirm-modal-body" style={{ width: 400 }}> <p className="reconfirm-modal-title">{title}</p> <p className="reconfirm-modal-content">{content}</p> <div className="reconfirm-modal-footer"> <Button size="large" onClick={onClose} block> {cancelText || "取消"} </Button> <Button type="primary" size="large" block onClick={onConfirm}> {okText || "确认"} </Button> </div> </div> </div> ); }
-
引用二次确认弹窗,进行简单交互
export default function ReconfirmModalPage() { const [visible, setVisible] = useState(false); return ( <div> <Button danger onClick={() => setVisible(true)}> 删除 </Button> {visible && ( <ReconfirmModal onConfirm={() => { setVisible(false); alert("点击了确认按钮"); }} onClose={() => { setVisible(false); alert("点击了取消按钮"); }} options={{ title: "删除提示", content: "确认删除吗?" }} /> )} </div> ); }
-
看看展示效果
有机结合Promise
我们需要梳理清晰我们的目的,然后再动手进行实现,通过理论指导实践。
首先创建一个方法,调用这个方法时唤醒二次确认弹窗,同时这个方法需要返回二次确认弹窗的交互结果。
通过ReactDOM.render方法将弹窗组件挂在到新创建的root节点上,将Promise的resolve方法传递给弹窗组件的两个按钮事件,此时用户点击按钮时即触发resove方法,Promise状态就从pending状态转移到fulfilled状态。
调用该方法的宿主方法即可得到用户点击二次确认弹窗的点击结果,我们的目的也就达到了。
export const openReconfirmModal = (options: ModalOption) => {
return new Promise((resolve) => {
const root = document.createElement('div')
document.body.appendChild(root)
// 移除react组件和DOM节点
const removeModal = () => {
ReactDOM.unmountComponentAtNode(root)
document.body.removeChild(root)
}
// 点击取消按钮
const onClose = () => {
removeModal()
resolve(false)
}
// 点击确认按钮
const onConfirm = () => {
removeModal()
resolve(true)
}
ReactDOM.render(createElement(ReconfirmModal, { onClose, onConfirm, options }), root)
})
}
调用二次确认弹窗方法
function ReconfirmModalPage() {
const handlePromseReconfirm = async () => {
// !!! **直接这么调用就完事儿,简单直接**
const res = await openReconfirmModal({
title: "删除提示",
content: "确认删除吗?",
});
if (res) {
alert("点击了确认按钮");
} else {
alert("点击了取消按钮");
}
};
return (
<Button danger type="primary" onClick={handlePromseReconfirm}>
Promise模式删除
</Button>
);
}
缺点分析
因为我们是通过ReactDOM.render方法渲染的弹窗,这就意味着弹窗组件跟主应用App的状态是隔离的,如果弹窗中用到Redux这种需要上下文Provider能力的工具,就需要在弹窗组件中也初始化一次。但是对于像二次确认弹窗这种简单的纯展示的组件,结合Promise的方式是完全可行的。
总结
日常开发工作中总有一些能够优化的步骤和流程,我们能够多思考一下,转变思路,就能够让我们的代码更加赏心悦目。
原文链接
https://www.levenx.com/article/the-organic-combination-of-pop-up-components-and-promise
个人博客网站,记录更多更全面的内容,
“当你点赞的时候,触碰的不是冰冷的按钮,而是作者那颗感恩的心。”
——鲁迅《非我所言》