Modal (Acro Design) 源码简析

991 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

开始 modal 前,先来看一下 teleport 组件:

teleport.gif

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>

modal-layout.png

可以看到,基本布局如下

  • 外层 container
    • mask “灰色”遮罩层
    • modal 的 wrapper
      • modal
        • header
        • body
        • footer

2. Hooks

hooks说明
useOverflow当 modal 弹出时,给 body(container)设置额外样式(例如增加样式 overflow: hidden)
usePopupManager管理所有的 dialog、popup、message 的增删(返回 z-index)
useDraggable拖拽
useTeleportContainer用于辅助找到 teleport 需要挂在到哪个 container 下
useI18n多语言

对于 hooks ,简单理解就是:把可以单独抽离出来的逻辑,交给小弟去做。

3. 点击事件

emit 给上层的事件,官网都有详细说明,这里不多赘述。 源码中还有一些内部监听的事件

  1. 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();
  }
};
  1. 点击 mask,关闭 modal 事件

这里很有意思,并不是我想象中的,给 mask 加一个点击事件,而是给 modal 的父布局 wrapper 加了两个事件

modal-event.png

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. 启发

  1. 多个变量同时作用,使用 computed 进行“聚合”
  2. 动态设置 class 的方式