前端开发elementUI

elementUI——MessageBox组件源码分析

2020-05-17  本文已影响0人  videring

说明:以下基于elementUI@2.13.1。

elementUI 弹框示例

从场景上说,MessageBox 的作用是美化系统自带的 alert、confirm 和 prompt,因此适合展示较为简单的内容。如果需要弹出较为复杂的内容,请使用 Dialog。

图1:原生alert
图2:原生confirm
图3:原生prompt

本次主要分析MessageBox以及基于MessageBoxalertconfirmprompt
阅读以下内容的前提是对官网示例和组件用法有了基本了解。

在elementUI的src/index.js中:

  Vue.prototype.$msgbox = MessageBox;
  Vue.prototype.$alert = MessageBox.alert;
  Vue.prototype.$confirm = MessageBox.confirm;
  Vue.prototype.$prompt = MessageBox.prompt;

$msgbox本质上就是MessageBox,而其他三个方法($alert$confirm$prompt)是对MessageBox的再封装。

1. 弹框对应的单文件及基本组成

具体代码见packages/message-box/src/main.vue单文件组件(见源码为方便后文说明,取名msgboxVue)。
整体而言,如下图所示,弹框分三个部分,header(标题+关闭按钮)、content(message+input)和btns(取消+确定)

图4
1.1 聊聊主要涉及哪些options:

1.2 聊聊弹框上下左右居中的样式实现

.el-message-box__wrapper {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    text-align: center;
}
图5
.el-message-box__wrapper:after {
    content: "";
    display: inline-block;
    height: 100%;
    width: 0;
    vertical-align: middle;
}

1.3 msgboxVue单文件中混入popup及popup-manager:

1.3.1 PopupManager的作用主要是设置蒙版,当有多级蒙版时,能在此进行统一管理:

1.3.2 popup.js是一个mixin混入,详见[element-ui 源码分析-工具篇:popup](https://segmentfault.com/a/1190000020242564),功能清单如下:

msgboxVue单文件中混入popup,主要用到一个prop和两个方法,由于popup和popup-manager是通用方法,多个组件用到,功能较多,在这里主要介绍在msgboxVue中用到的:

1.4 msgboxVue单文件:
在上一小节聊完了重要的且相对独立的popup及popup-manager,接下来聊聊msgboxVue组件的其他功能。
1.4.1 图4中弹框右上角关闭按钮点击事件

图6:弹框右上角关闭按钮执行流程
流程走到最后会执行doClose方法,这个在1.3.2最后已介绍。
1.4.2 图4中input框enter事件和“确定”按钮点击事件
@keydown.enter.native="handleInputEnter"
handleInputEnter() {
        if (this.inputType !== 'textarea') {
          return this.handleAction('confirm');
        }
}

可以发现,最后会执行handleAction方法,跟上一小节一样,只不过action变成了“confirm”,如果校验合法,会关闭弹框和蒙版。
1.4.3 图4中弹框取消按钮点击事件

@click.native="handleAction('cancel')"

同上,只不过action变成了“cancel”。
讲到这,msgboxVue单文件就基本讲完了,接下来就是另一个重头戏——对msgboxVue单文件的二次封装:$msgbox$alert$confirm$prompt

2. 对msgboxVue单文件组件的封装:MessageBox(即$msgbox)

官方示例中,可以发现,可以通过函数调用的方式生成弹框(本质是对第1节中msgboxVue单文件组件的调用),如果支持promise,当点击“确定”按钮时,执行then方法;当点击“取消”按钮或右上角关闭按钮时,执行catch方法;或者通过传入callback回调函数的方式,来处理“确定”、“取消”和“关闭”等。
msgbox本质上就是MessageBox,而其他三个方法(alert、confirm和prompt)是对MessageBox的再封装,在这里首先分析MessageBox。
先上MessageBox`源码:

const MessageBox = function(options, callback) {
  if (Vue.prototype.$isServer) return;
  if (typeof options === 'string' || isVNode(options)) {
    options = {
      message: options
    };
    if (typeof arguments[1] === 'string') {
      options.title = arguments[1];
    }
  } else if (options.callback && !callback) {
    callback = options.callback;
  }

  if (typeof Promise !== 'undefined') {
    return new Promise((resolve, reject) => {
      msgQueue.push({
        options: merge({}, defaults, MessageBox.defaults, options),
        callback: callback,
        resolve: resolve,
        reject: reject
      });

      showNextMsg();
    });
  } else {
    msgQueue.push({
      options: merge({}, defaults, MessageBox.defaults, options),
      callback: callback
    });

    showNextMsg();
  }
};

上面代码,主要做两件事:
a. 对messagecallback做处理
b. msgQueue数组保存options和callback,如果浏览器支持Promise,那么再将resovlereject封装进msgQueue,分别触发后续thencatch逻辑,可见下面的官方示例:
// 官方示例

<template>
  <el-button type="text" @click="open">点击打开 Message Box</el-button>
</template>

<script>
  export default {
    methods: {
      open() {
        const h = this.$createElement;
        this.$msgbox({
          title: '消息',
          message: h('p', null, [
            h('span', null, '内容可以是 '),
            h('i', { style: 'color: teal' }, 'VNode')
          ]),
          showCancelButton: true,
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          beforeClose: (action, instance, done) => {
            if (action === 'confirm') {
              instance.confirmButtonLoading = true;
              instance.confirmButtonText = '执行中...';
              setTimeout(() => {
                done();
                setTimeout(() => {
                  instance.confirmButtonLoading = false;
                }, 300);
              }, 3000);
            } else {
              done();
            }
          }
        }).then(action => {
          this.$message({
            type: 'info',
            message: 'action: ' + action
          });
        });
      }
    }
  }
</script>
图7:messagebox流程图
其中callback,如果没有往messagebox中传入callback,则使用默认值defaultCallback,如果支持promise,当action为 'confirm'(点击确认按钮或input框按enter键)时,调用下一步的then方法;当action为'cancel'或'close'(点击取消或关闭按钮)时,调用下一步的catch方法。其实现如下:
const defaultCallback = action => {
  if (currentMsg) { // 从msgQueue数组头部取出
    let callback = currentMsg.callback;
    if (typeof callback === 'function') { // 调用用户传入的callback
      if (instance.showInput) {
        callback(instance.inputValue, action);
      } else {
        callback(action);
      }
    }
    if (currentMsg.resolve) {
      if (action === 'confirm') {
        if (instance.showInput) {
          currentMsg.resolve({ value: instance.inputValue, action }); // 如果支持promise,当action为 'confirm'时,调用下一步的then方法
        } else {
          currentMsg.resolve(action);
        }
      } else if (currentMsg.reject && (action === 'cancel' || action === 'close')) {
        currentMsg.reject(action); // 如果支持promise,当action为'cancel'或'close'时,调用下一步的catch方法
      }
    }
  }
};

$alert$confirm$prompt是对MessageBox的再次简单封装,例如:

// element-ui\packages\message-box\src\main.js
MessageBox.prompt = (message, title, options) => {
  if (typeof title === 'object') {
    options = title;
    title = '';
  } else if (title === undefined) {
    title = '';
  }
  return MessageBox(merge({
    title: title,
    message: message,
    showCancelButton: true,
    showInput: true,
    $type: 'prompt'
  }, options));
};

// element-ui\src\index.js
Vue.prototype.$prompt = MessageBox.prompt;

最后,举一反三,Loading、Notification和Message也有类似做法,通过对组件进行二次封装,对外提供了函数调用方式。

  Vue.prototype.$loading = Loading.service;
  Vue.prototype.$notify = Notification;
  Vue.prototype.$message = Message;
上一篇下一篇

猜你喜欢

热点阅读