携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情
开始 modal 前,先来看一下 teleport 组件:
AcroDesign 的 Modal,是基于 Vue 的 Teleport 实现的。简单的说,teleport 实现了弹框效果,而 modal 是对 teleport 的二次封装,更加易用,也更加贴近实际使用。
阅读本文,你将会了解以下内容
- modal 的 template 布局是怎样的?
- 其中应用了哪些 hooks?
- 内部应用了哪些事件?
- 其 style 是怎样使用的?
- 等等
1. template 布局
源码中,Modal 组件的最外层是 Teleport,但实际通过 F12 发现,并不能找到 teleport,而是在 body 下,直接看到了我们的 modal 布局。
实际上,是 Teleport 帮我们把 Modal 布局挂载到了 body 节点下
<body>
<div>主界面</div>
<!-- modal 的布局 -->
<div class="arco-modal-container" style="z-index: 1001;">
<div class="arco-modal-mask" style=""></div>
<div class="arco-modal-wrapper arco-modal-wrapper-align-center arco-modal-wrapper-moved">
<div class="arco-modal arco-modal-draggable" style="transform: translate(715px, 348.5px);">
<div class="arco-modal-header">
...
</div>
<div class="arco-modal-body">
...
</div>
<div class="arco-modal-footer">
...
</div>
</div>
</div>
</div>
</body>
可以看到,基本布局如下
- 外层 container
- mask “灰色”遮罩层
- modal 的 wrapper
- modal
- header
- body
- footer
- modal
2. Hooks
| hooks | 说明 |
|---|---|
| useOverflow | 当 modal 弹出时,给 body(container)设置额外样式(例如增加样式 overflow: hidden) |
| usePopupManager | 管理所有的 dialog、popup、message 的增删(返回 z-index) |
| useDraggable | 拖拽 |
| useTeleportContainer | 用于辅助找到 teleport 需要挂在到哪个 container 下 |
| useI18n | 多语言 |
对于 hooks ,简单理解就是:把可以单独抽离出来的逻辑,交给小弟去做。
3. 点击事件
emit 给上层的事件,官网都有详细说明,这里不多赘述。 源码中还有一些内部监听的事件
- watch 了modal的显隐控制变量:computedVisible,当 modal 显示时,注册全局事件,用于监听 ESC 按键,目的是按下 ESC 按键时隐藏 modal
// 添加全局监听
const addGlobalKeyDownListener = () => {
if (props.escToClose && !globalKeyDownListener) {
globalKeyDownListener = true;
on(document.documentElement, 'keydown', handleGlobalKeyDown);
}
};
// 当按下 esc 时,触发 隐藏 modal 的逻辑
const handleGlobalKeyDown = (ev: KeyboardEvent) => {
if (props.escToClose && ev.key === KEYBOARD_KEY.ESC && isLastDialog()) {
handleCancel();
}
};
- 点击 mask,关闭 modal 事件
这里很有意思,并不是我想象中的,给 mask 加一个点击事件,而是给 modal 的父布局 wrapper 加了两个事件
const handleMaskMouseDown = (ev: Event) => {
if (ev.target === wrapperRef.value) {
// MouseDown 事件触发,将 currentIsMask 设置为 true
currentIsMask.value = true;
}
};
const handleMaskClick = () => {
// click 事件触发,判断 currentIsMask 为 true,执行关闭 modal 操作
if (props.mask && props.maskClosable && currentIsMask.value) {
handleCancel();
}
};
这里给大家留一个思考题,为什么要搞两个点击事件来触发关闭 modal 的逻辑呢?
(好吧,我摊牌了。其实是这里我没理解上去,烦请大佬指点小弟一下)
4. 样式
样式的内容没啥难的,主要是说一下组件中,通过 computed 动态设置样式。以 modal 的样式为例:
<div :class="modalCls" ...>
const modalCls = computed(() => [
`${prefixCls}`,
props.modalClass,
{
[`${prefixCls}-simple`]: props.simple,
[`${prefixCls}-draggable`]: mergedDraggable.value,
[`${prefixCls}-fullscreen`]: props.fullscreen,
},
]);
- prefixCls 为默认样式
- props.modalClass 为用户自定义的样式
- -simple, -draggable, -fullscreen 为根据传参,是否动态添加此样式
5. 启发
- 多个变量同时作用,使用 computed 进行“聚合”
- 动态设置 class 的方式