我们在eBay使用React创建各种内部的前端应用, 在构建复杂前端应用时模态框或者说弹出框被大量的使用。
模态框单独打开一个窗口为用户提供额外的信息,行为类似于浏览器打开一个新的tab页,但是用户不能做其他操作直到关闭模态框。
去年,我们开发了一个新的方案管理模态框用来帮助eBay简化工作流并支持更多的项目,我们最近开源了这个新的模态框管理方案并分享到了技术社区。
我们如何重塑模态框?
在SPA应用里,我们通常使用history.push('/some-page')来切换页面,但是在模态框中,我们并没有一个像 modal.show/hide 这种简单的方式管理模态框,常规流程中,我们在项目里使用一个函数管理模态框。
例如我们要创建一个类似于如下的模态框:
用对话框创建一个JIRA 票据,他可以在很多地方被弹出,例如header,菜单,列表,我们需要用jsx来声明一个模态框,这时候问题就来了: 我们应该在哪声明jsx?
最常见的选择是声明在使用它的地方,但是使用模态框不仅仅是声明一个jsx,还包括模态框的可见性(visibility、组件包含的参数等等,如果我们回过头来比较页面跳转和模态框的场景,我们可以设计出想路由管理那样简单的模态框管理API,。
// Router definition for pages
<Route path="/hello-world" component={HelloWorld} />
<Route path="/first-page" component={FirstPage} />
// Modals declaration
<ModalDef id="/jira/create" component={CreateJiraModal} />
<ModalDef id="/user/edit" component={UserEditModal} />
这时,我们可以设计好的模态框管理API去管理模态框,例如
ModalManager.show('/jira/create');
// or with some props passed to the modal component
ModalManager.show('/user/edit', { userId: 666 });
隐藏模态框,我们可以使用ModalManager.hid('/user/edit'), 模态框管理API提供一个高阶组件用来优雅的处理模态框的显示和隐藏。
基于这个想法,我们创建了一个叫nice-modal-react的实用程序帮助我们管理模态框,使用一年后,我们做了开源,开源地址:github.com/eBay/nice-m…
通常,我们使用React Context来保存模态框的状态,它提供了友好的API来管理状态,更多的使用方法,请访问[README],它提供了非常灵活的API来解决各种问题。
现在,让我们看一个简单的demo。
创建模态框
创建一个模态框,你可以使用高阶组件,这里使用Ant.Design的模态框来举例。
import { Modal } from 'antd';
import NiceModal, { useModal } from '@ebay/nice-modal-react';
const HelloModal = NiceModal.create(({ name }) => {
// Use the hook to manage modal state
const modal = useModal();
return (
<Modal
title="Hello Antd"
onOk={() => modal.hide()}
onCancel={() => modal.hide()}
afterClose={() => { modal.hideResolve(); modal.remove(); }}
>
Hello ${name}!
</Modal>
);
});
export default HelloModal;
通过使用create方法创建一个高阶组件包裹模态框,他确保组件代码在可见之前不被执行,同时在不可见的时候一处dom元素,模态框的显示和隐藏方法也保持不变。
userModal hook通过符合习惯的方式返回一个模态框的句柄来管理模态框,你可以不通过组件外的可见性状态变量,而是在模态框内部关闭模态框
使用NickModal
创建一个模态框之后,你可以使用NiceModal API,首先,使用NiceModal.Provider包裹你的应用:
import NiceModal from '@ebay/nice-modal-react';
ReactDOM.render(
<React.StrictMode>
<NiceModal.Provider>
<App />
</NiceModal.Provider>
</React.StrictMode>,
document.getElementById('root'),
);
然后你可以使用一个hook来完成管理
import NiceModal, { useModal } from '@ebay/nice-modal-react';
import HelloModal from './HelloModal';
// ...
const modal = useModal(HelloModal);
// Show the modal and pass arguments as props
modal.show({ name: 'Nate' });
// ...
除了基本用法,NiceModal还提供了灵活的API,例如:
// Use modal component with the global method
NiceModal.show(UserInfoModal, { userId: 666 });
// Or using React hook
const modal = NiceModal.useModal(UserEditModal);
// You can also use it by id
const modal = NiceModal.useModal('/user/info/edit')
// Show the modal
modal.show({ userId: 666 });
// After use info updated, refresh the user list
modal.show({ userId: 666 }).then(refreshUserList);
// Wait for the hide transition
await modal.hide();
NOTE: if you use the modal by id, then you need to declare it somewhere or register it:
// Declare the modal directly
<UserInfoModal id="/user/info/edit" />
// or by register api
NiceModal.register('/user/info/edit', UserInfoModal)
// or by ModalDef
<ModalDef id='/user/info/edit' component={UserInfoModal} />
优势
- 很容易的将模态框单独拆分出来, 解耦代码
- 无需控制,你可以在模态框里关闭自身
- 解耦的,你在使用前不需要inport 模态框组件,模态框可以通过id来管理, 就像用React router管理page一样
- 你的代码在模态框可见之前不会执行
- 基于Promise,父组件除了使用props传递参数给子组件外,也可以通过Promise传递
- 他是无依赖的, 可以方便和其他UI框架集成
最后
这篇文章中我们介绍了一种新的模态框管理方式,这种方式已经开源:github.com/eBay/nice-m…
在线Demo: opensource.ebay.com/nice-modal-…